@effect-agent/platform-cloudflare 0.1.0-beta.9 → 0.1.0-beta.91

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.
Files changed (100) hide show
  1. package/dist/Alarm.d.mts +188 -0
  2. package/dist/Alarm.mjs +451 -0
  3. package/dist/Alarm.mjs.map +1 -0
  4. package/dist/BrowserRestCapture.d.mts +35 -0
  5. package/dist/BrowserRestCapture.mjs +238 -0
  6. package/dist/BrowserRestCapture.mjs.map +1 -0
  7. package/dist/BrowserRestCrawl.d.mts +17 -0
  8. package/dist/BrowserRestCrawl.mjs +244 -0
  9. package/dist/BrowserRestCrawl.mjs.map +1 -0
  10. package/dist/CloudflareAiGateway.d.mts +64 -0
  11. package/dist/CloudflareAiGateway.mjs +70 -0
  12. package/dist/CloudflareAiGateway.mjs.map +1 -0
  13. package/dist/CloudflareBindings.d.mts +115 -0
  14. package/dist/CloudflareBindings.mjs +113 -0
  15. package/dist/CloudflareBindings.mjs.map +1 -0
  16. package/dist/CloudflareBrowser-DV6kya1H.mjs +495 -0
  17. package/dist/CloudflareBrowser-DV6kya1H.mjs.map +1 -0
  18. package/dist/CloudflareBrowser-Doe1M7R5.d.mts +80 -0
  19. package/dist/CloudflareBrowser.d.mts +2 -0
  20. package/dist/CloudflareBrowser.mjs +2 -0
  21. package/dist/CloudflareCodeMode.d.mts +44 -0
  22. package/dist/CloudflareCodeMode.mjs +616 -0
  23. package/dist/CloudflareCodeMode.mjs.map +1 -0
  24. package/dist/CloudflareConfig-f3CqTel1.d.mts +101 -0
  25. package/dist/CloudflareConfig.d.mts +2 -0
  26. package/dist/CloudflareConfig.mjs +122 -0
  27. package/dist/CloudflareConfig.mjs.map +1 -0
  28. package/dist/CloudflareMemory.d.mts +142 -0
  29. package/dist/CloudflareMemory.mjs +202 -0
  30. package/dist/CloudflareMemory.mjs.map +1 -0
  31. package/dist/CloudflareScheduling.d.mts +52 -0
  32. package/dist/CloudflareScheduling.mjs +396 -0
  33. package/dist/CloudflareScheduling.mjs.map +1 -0
  34. package/dist/CloudflareSubscriptions.d.mts +87 -0
  35. package/dist/CloudflareSubscriptions.mjs +530 -0
  36. package/dist/CloudflareSubscriptions.mjs.map +1 -0
  37. package/dist/CloudflareThreadClient.d.mts +1496 -0
  38. package/dist/CloudflareThreadClient.mjs +382 -0
  39. package/dist/CloudflareThreadClient.mjs.map +1 -0
  40. package/dist/InteractiveBrowser-DUE_m12-.d.mts +161 -0
  41. package/dist/InteractiveBrowser-DupDtfw2.mjs +1169 -0
  42. package/dist/InteractiveBrowser-DupDtfw2.mjs.map +1 -0
  43. package/dist/InteractiveBrowser.d.mts +2 -0
  44. package/dist/InteractiveBrowser.mjs +2 -0
  45. package/dist/ProtectedBrowser.d.mts +123 -0
  46. package/dist/ProtectedBrowser.mjs +1135 -0
  47. package/dist/ProtectedBrowser.mjs.map +1 -0
  48. package/dist/ThreadObject-CIbmXZCY.d.mts +812 -0
  49. package/dist/ThreadObject-DvRG0dDz.mjs +835 -0
  50. package/dist/ThreadObject-DvRG0dDz.mjs.map +1 -0
  51. package/dist/ThreadObject.d.mts +2 -0
  52. package/dist/ThreadObject.mjs +3 -0
  53. package/dist/WakeScheduler.d.mts +23 -0
  54. package/dist/WakeScheduler.mjs +57 -0
  55. package/dist/WakeScheduler.mjs.map +1 -0
  56. package/dist/boundary-BguazVkh.mjs +42 -0
  57. package/dist/boundary-BguazVkh.mjs.map +1 -0
  58. package/dist/index.d.mts +13 -1548
  59. package/dist/index.mjs +13 -1636
  60. package/dist/prepared-admission-DU0_T-ev.mjs +73 -0
  61. package/dist/prepared-admission-DU0_T-ev.mjs.map +1 -0
  62. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  63. package/package.json +1 -53
  64. package/src/Alarm.ts +973 -0
  65. package/src/BrowserRestCapture.ts +477 -0
  66. package/src/BrowserRestCrawl.ts +540 -0
  67. package/src/CloudflareAiGateway.ts +170 -0
  68. package/src/CloudflareBindings.ts +199 -0
  69. package/src/CloudflareBrowser.ts +15 -0
  70. package/src/{code-mode-executor.ts → CloudflareCodeMode.ts} +372 -204
  71. package/src/{config.ts → CloudflareConfig.ts} +9 -8
  72. package/src/CloudflareMemory.ts +420 -0
  73. package/src/CloudflareScheduling.ts +747 -0
  74. package/src/CloudflareSubscriptions.ts +1041 -0
  75. package/src/CloudflareThreadClient.ts +809 -0
  76. package/src/InteractiveBrowser.ts +2354 -0
  77. package/src/ProtectedBrowser.ts +9 -0
  78. package/src/ThreadObject.ts +1179 -0
  79. package/src/{wake-scheduler.ts → WakeScheduler.ts} +32 -26
  80. package/src/index.ts +12 -23
  81. package/src/internal/boundary.ts +55 -0
  82. package/src/internal/browser-quick-action.ts +857 -0
  83. package/src/internal/browser-session-lifecycle.ts +160 -0
  84. package/src/internal/layers.ts +751 -0
  85. package/src/internal/message-delivery.ts +152 -0
  86. package/src/internal/prepared-admission.ts +116 -0
  87. package/src/internal/progress-wait.ts +121 -0
  88. package/src/internal/transport.ts +44 -0
  89. package/src/protected-browser/binding.ts +313 -0
  90. package/src/protected-browser/host.ts +344 -0
  91. package/src/protected-browser/inspect-frame.ts +125 -0
  92. package/src/protected-browser/native.ts +445 -0
  93. package/src/protected-browser/policy.ts +821 -0
  94. package/dist/index.mjs.map +0 -1
  95. package/src/alarm.ts +0 -334
  96. package/src/bindings.ts +0 -145
  97. package/src/client.ts +0 -646
  98. package/src/conversation-object.ts +0 -768
  99. package/src/layers.ts +0 -376
  100. package/src/transport.ts +0 -38
@@ -0,0 +1,244 @@
1
+ import { Duration, Effect, Layer, Option, Redacted, Ref, Schedule, Schema, Scope, Stream } from "effect";
2
+ import { SandboxImplementation } from "effect-agent/sandbox";
3
+ import { HttpClient, HttpClientRequest } from "effect/unstable/http";
4
+ import { PageCrawl, PageCrawlLimitError, PageCrawlProtocolError, PageCrawlRateLimitedError, PageCrawlRecord, PageCrawlTerminalError } from "effect-agent/page-crawl";
5
+ //#region src/BrowserRestCrawl.ts
6
+ /** Node-safe REST crawl implementation. It never exposes or persists provider job identity. */
7
+ const browserRestCrawlImplementation = SandboxImplementation.make({
8
+ isolation: "isolated",
9
+ identity: "cloudflare-browser-rest-crawl"
10
+ });
11
+ const API_ORIGIN = "https://api.cloudflare.com";
12
+ const POLL_INTERVAL = Duration.seconds(1);
13
+ const CANCEL_TIMEOUT = Duration.seconds(30);
14
+ const MAX_CONTROL_RESPONSE_BYTES = 65536;
15
+ const MAX_RESULTS_RESPONSE_BYTES = 12582912;
16
+ const MAX_DIAGNOSTIC_LENGTH = 8e3;
17
+ const MAX_CURSOR_LENGTH = 1024;
18
+ const MAX_RECORDS_PER_RESPONSE = 1e4;
19
+ const BoundedJobId = Schema.NonEmptyString.check(Schema.isMaxLength(256));
20
+ const BoundedCursorString = Schema.NonEmptyString.check(Schema.isMaxLength(MAX_CURSOR_LENGTH));
21
+ const ProviderCursor = Schema.Union([BoundedCursorString, Schema.Natural]);
22
+ const ProviderJobStatus = Schema.Literals([
23
+ "running",
24
+ "completed",
25
+ "errored",
26
+ "cancelled_by_user",
27
+ "cancelled_due_to_timeout",
28
+ "cancelled_due_to_limits"
29
+ ]);
30
+ const ProviderResult = Schema.Struct({
31
+ id: BoundedJobId,
32
+ status: ProviderJobStatus,
33
+ browserSecondsUsed: Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0))),
34
+ total: Schema.optionalKey(Schema.Natural),
35
+ finished: Schema.optionalKey(Schema.Natural),
36
+ skipped: Schema.optionalKey(Schema.Natural),
37
+ records: Schema.Array(PageCrawlRecord).check(Schema.isMaxLength(MAX_RECORDS_PER_RESPONSE)),
38
+ cursor: Schema.optionalKey(ProviderCursor)
39
+ });
40
+ const CreateEnvelope = Schema.Struct({
41
+ success: Schema.Literal(true),
42
+ result: BoundedJobId
43
+ });
44
+ const ResultEnvelope = Schema.Struct({
45
+ success: Schema.Literal(true),
46
+ result: ProviderResult
47
+ });
48
+ const DeleteEnvelope = Schema.Struct({
49
+ success: Schema.Literal(true),
50
+ result: Schema.Struct({
51
+ job_id: BoundedJobId,
52
+ message: Schema.String.check(Schema.isMaxLength(MAX_DIAGNOSTIC_LENGTH))
53
+ })
54
+ });
55
+ const boundedDiagnostic = (message) => message.slice(0, MAX_DIAGNOSTIC_LENGTH);
56
+ const privateCause = (value, apiToken) => {
57
+ const raw = boundedDiagnostic(String(value));
58
+ if (raw.length === 0) return void 0;
59
+ const token = Redacted.value(apiToken);
60
+ return new Error(token.length === 0 ? raw : raw.replaceAll(token, "[REDACTED]"));
61
+ };
62
+ const protocolError = (message, cause) => PageCrawlProtocolError.make({
63
+ implementation: browserRestCrawlImplementation,
64
+ message: boundedDiagnostic(message),
65
+ ...cause === void 0 ? {} : { cause }
66
+ });
67
+ const limitError = (request, limit, observed) => {
68
+ const maximum = limit === "pages" ? request.limits.maxPages : limit === "page-bytes" ? request.limits.maxPageBytes : limit === "total-bytes" ? request.limits.maxTotalBytes : request.limits.deadlineMillis;
69
+ return PageCrawlLimitError.make({
70
+ implementation: browserRestCrawlImplementation,
71
+ limit,
72
+ maximum,
73
+ observed,
74
+ message: `The crawl exceeded its ${limit} limit`
75
+ });
76
+ };
77
+ const retryAfterMillis = (headers) => {
78
+ const seconds = Number(headers["retry-after"]);
79
+ if (!Number.isSafeInteger(seconds) || seconds < 0) return void 0;
80
+ const millis = seconds * 1e3;
81
+ return Number.isSafeInteger(millis) ? millis : void 0;
82
+ };
83
+ const isJsonResponse = (headers) => {
84
+ const contentType = headers["content-type"];
85
+ if (contentType === void 0) return false;
86
+ const mediaType = contentType.split(";", 1)[0]?.trim().toLowerCase();
87
+ return mediaType === "application/json" || mediaType?.endsWith("+json") === true;
88
+ };
89
+ const isQuotaMessage = (bodyText) => /time limit|daily|quota/i.test(bodyText);
90
+ const endpoint = (options, jobId) => `${API_ORIGIN}/client/v4/accounts/${encodeURIComponent(options.accountId)}/browser-rendering/crawl${jobId === void 0 ? "" : `/${encodeURIComponent(jobId)}`}`;
91
+ const readBoundedResponse = Effect.fn("BrowserRestCrawl.readBoundedResponse")(function* (response, maximum) {
92
+ const decoder = new TextDecoder("utf-8", {
93
+ fatal: true,
94
+ ignoreBOM: false
95
+ });
96
+ const state = yield* Stream.runFoldEffect(response.stream, () => ({
97
+ observed: 0,
98
+ text: ""
99
+ }), (current, chunk) => {
100
+ const observed = current.observed + chunk.byteLength;
101
+ if (observed > maximum) return Effect.fail(protocolError(`The Browser Run crawl response exceeded ${String(maximum)} transport bytes`));
102
+ return Effect.try({
103
+ try: () => ({
104
+ observed,
105
+ text: current.text + decoder.decode(chunk, { stream: true })
106
+ }),
107
+ catch: (cause) => protocolError("Decoding the Browser Run crawl response failed", cause)
108
+ });
109
+ }).pipe(Effect.mapError((cause) => Schema.is(PageCrawlProtocolError)(cause) ? cause : protocolError("Reading the Browser Run crawl response failed", cause)));
110
+ return yield* Effect.try({
111
+ try: () => state.text + decoder.decode(),
112
+ catch: (cause) => protocolError("Decoding the Browser Run crawl response failed", cause)
113
+ });
114
+ });
115
+ const deadlineFailure = Effect.fn("BrowserRestCrawl.deadlineFailure")(function* (request, startedAt) {
116
+ const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
117
+ return yield* limitError(request, "deadline", Math.max(0, now - startedAt));
118
+ });
119
+ const withinDeadline = Effect.fn("BrowserRestCrawl.withinDeadline")(function* (effect, request, startedAt) {
120
+ const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
121
+ const elapsed = Math.max(0, now - startedAt);
122
+ const remaining = request.limits.deadlineMillis - elapsed;
123
+ if (remaining <= 0) return yield* limitError(request, "deadline", elapsed);
124
+ return yield* effect.pipe(Effect.timeoutOrElse({
125
+ duration: Duration.millis(remaining),
126
+ orElse: () => deadlineFailure(request, startedAt)
127
+ }));
128
+ });
129
+ const checkDeadline = Effect.fn("BrowserRestCrawl.checkDeadline")(function* (request, startedAt) {
130
+ const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
131
+ const elapsed = Math.max(0, now - startedAt);
132
+ if (elapsed >= request.limits.deadlineMillis) return yield* limitError(request, "deadline", elapsed);
133
+ });
134
+ const normalizedCursor = (cursor) => String(cursor);
135
+ const sameHost = (expectedHost, url) => new URL(url).host === expectedHost;
136
+ const makeCrawl = (client, options) => (input) => Stream.unwrap(Effect.gen(function* () {
137
+ const startedAt = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
138
+ const startHost = new URL(input.startUrl).host;
139
+ const executeJson = Effect.fn("BrowserRestCrawl.executeJson")(function* (request, schema, maximum) {
140
+ const authorized = HttpClientRequest.bearerToken(request, options.apiToken).pipe(HttpClientRequest.acceptJson);
141
+ const response = yield* client.execute(authorized).pipe(Effect.mapError((cause) => protocolError("Calling the Browser Run crawl REST API failed", privateCause(cause, options.apiToken))));
142
+ const bodyText = yield* readBoundedResponse(response, maximum);
143
+ if (response.status === 429) {
144
+ const reason = isQuotaMessage(bodyText) ? "quota" : "rate";
145
+ return yield* PageCrawlRateLimitedError.make({
146
+ implementation: browserRestCrawlImplementation,
147
+ reason,
148
+ ...retryAfterMillis(response.headers) === void 0 ? {} : { retryAfterMillis: retryAfterMillis(response.headers) },
149
+ message: reason === "quota" ? "Browser Run exceeded its browser quota" : "Browser Run crawl was rate limited",
150
+ ...privateCause(bodyText, options.apiToken) === void 0 ? {} : { cause: privateCause(bodyText, options.apiToken) }
151
+ });
152
+ }
153
+ if (response.status < 200 || response.status >= 300) return yield* protocolError(`Browser Run crawl answered HTTP ${String(response.status)}`, privateCause(bodyText, options.apiToken));
154
+ if (!isJsonResponse(response.headers)) return yield* protocolError("The Browser Run crawl success response was not JSON", privateCause(bodyText, options.apiToken));
155
+ return yield* Schema.decodeEffect(Schema.fromJsonString(schema))(bodyText).pipe(Effect.mapError((cause) => protocolError("Browser Run returned a malformed crawl response", privateCause(cause, options.apiToken))));
156
+ });
157
+ const createJob = Effect.gen(function* () {
158
+ const requestWithBody = yield* HttpClientRequest.post(endpoint(options)).pipe(HttpClientRequest.bodyJson({
159
+ url: input.startUrl,
160
+ crawlPurposes: [...input.purposes],
161
+ limit: input.limits.maxPages,
162
+ depth: input.limits.maxDepth,
163
+ formats: ["markdown"],
164
+ render: true,
165
+ options: {
166
+ includeExternalLinks: false,
167
+ includeSubdomains: false
168
+ }
169
+ }), Effect.mapError((cause) => protocolError("Encoding the Browser Run crawl request failed", cause)));
170
+ return (yield* withinDeadline(executeJson(requestWithBody, CreateEnvelope, MAX_CONTROL_RESPONSE_BYTES), input, startedAt)).result;
171
+ });
172
+ const job = yield* Effect.uninterruptibleMask((restore) => restore(createJob).pipe(Effect.flatMap((id) => Effect.gen(function* () {
173
+ const state = yield* Ref.make("running");
174
+ const scope = yield* Effect.scope;
175
+ const cancelIfRunning = Ref.modify(state, (current) => current === "running" ? [true, "terminal"] : [false, "terminal"]).pipe(Effect.flatMap((shouldCancel) => {
176
+ if (!shouldCancel) return Effect.void;
177
+ return executeJson(HttpClientRequest.delete(endpoint(options, id)), DeleteEnvelope, MAX_CONTROL_RESPONSE_BYTES).pipe(Effect.flatMap((deleted) => deleted.result.job_id === id ? Effect.void : protocolError("Browser Run cancelled a different crawl job"))).pipe(Effect.timeoutOrElse({
178
+ duration: CANCEL_TIMEOUT,
179
+ orElse: () => protocolError("Cancelling the Browser Run crawl exceeded 30 seconds")
180
+ }));
181
+ }));
182
+ yield* Scope.addFinalizer(scope, cancelIfRunning.pipe(Effect.catchCause(() => Effect.logWarning("Browser Run crawl cancellation failed"))));
183
+ return {
184
+ id,
185
+ state
186
+ };
187
+ }))));
188
+ const fetchResult = Effect.fn("BrowserRestCrawl.fetchResult")(function* (cursor, statusOnly) {
189
+ let request = HttpClientRequest.get(endpoint(options, job.id));
190
+ if (statusOnly) request = HttpClientRequest.setUrlParam(request, "limit", "1");
191
+ if (Option.isSome(cursor)) request = HttpClientRequest.setUrlParam(request, "cursor", cursor.value);
192
+ const envelope = yield* withinDeadline(executeJson(request, ResultEnvelope, MAX_RESULTS_RESPONSE_BYTES), input, startedAt);
193
+ if (envelope.result.id !== job.id) return yield* protocolError("Browser Run returned a different crawl job identity");
194
+ return envelope.result;
195
+ });
196
+ const terminal = yield* Effect.uninterruptibleMask((restore) => restore(withinDeadline(Effect.repeat(fetchResult(Option.none(), true), {
197
+ schedule: Schedule.spaced(POLL_INTERVAL),
198
+ until: (result) => result.status !== "running"
199
+ }), input, startedAt)).pipe(Effect.tap(() => Ref.set(job.state, "terminal"))));
200
+ if (terminal.status === "running") return yield* protocolError("Browser Run polling stopped before a terminal status");
201
+ if (terminal.status !== "completed") return yield* PageCrawlTerminalError.make({
202
+ implementation: browserRestCrawlImplementation,
203
+ status: terminal.status,
204
+ message: `Browser Run crawl ended with status ${terminal.status}`
205
+ });
206
+ return Stream.paginate({
207
+ cursor: Option.none(),
208
+ seen: []
209
+ }, (state) => Effect.gen(function* () {
210
+ const result = yield* fetchResult(state.cursor, false);
211
+ if (result.status !== "completed") return yield* protocolError("Browser Run changed crawl status during result pagination");
212
+ if (result.cursor === void 0) return [result.records, Option.none()];
213
+ const cursor = normalizedCursor(result.cursor);
214
+ if (state.seen.includes(cursor)) return yield* protocolError("Browser Run repeated a crawl result cursor");
215
+ if (state.seen.length >= input.limits.maxPages) return yield* protocolError("Browser Run returned too many crawl result cursors");
216
+ return [result.records, Option.some({
217
+ cursor: Option.some(cursor),
218
+ seen: [...state.seen, cursor]
219
+ })];
220
+ })).pipe(Stream.rechunk(1), Stream.mapAccumEffect(() => ({
221
+ pages: 0,
222
+ totalBytes: 0
223
+ }), (state, record) => Effect.gen(function* () {
224
+ yield* checkDeadline(input, startedAt);
225
+ const pages = state.pages + 1;
226
+ if (pages > input.limits.maxPages) return yield* limitError(input, "pages", pages);
227
+ if (!sameHost(startHost, record.url)) return yield* protocolError("Browser Run returned an off-host crawl record");
228
+ if (record.metadata !== void 0 && !sameHost(startHost, record.metadata.url)) return yield* protocolError("Browser Run returned off-host crawl metadata");
229
+ const pageBytes = record.markdown === void 0 ? 0 : new TextEncoder().encode(record.markdown).byteLength;
230
+ if (pageBytes > input.limits.maxPageBytes) return yield* limitError(input, "page-bytes", pageBytes);
231
+ const totalBytes = state.totalBytes + pageBytes;
232
+ if (totalBytes > input.limits.maxTotalBytes) return yield* limitError(input, "total-bytes", totalBytes);
233
+ return [{
234
+ pages,
235
+ totalBytes
236
+ }, [record]];
237
+ })));
238
+ }));
239
+ /** Cloudflare REST PageCrawl Layer for Node and other non-Worker composition roots. */
240
+ const browserRestCrawlLayer = (options) => Layer.effect(PageCrawl, Effect.map(HttpClient.HttpClient, (client) => PageCrawl.of({ crawl: makeCrawl(client, options) })));
241
+ //#endregion
242
+ export { browserRestCrawlImplementation, browserRestCrawlLayer };
243
+
244
+ //# sourceMappingURL=BrowserRestCrawl.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BrowserRestCrawl.mjs","names":[],"sources":["../src/BrowserRestCrawl.ts"],"sourcesContent":["import {\n Duration,\n Effect,\n Layer,\n Option,\n Redacted,\n Ref,\n Schedule,\n Schema,\n Scope,\n Stream,\n} from \"effect\";\nimport {\n PageCrawl,\n PageCrawlLimitError,\n PageCrawlProtocolError,\n PageCrawlRateLimitedError,\n PageCrawlRecord,\n PageCrawlTerminalError,\n type PageCrawlCrawl,\n type PageCrawlError,\n type PageCrawlRequest,\n} from \"effect-agent/page-crawl\";\nimport { SandboxImplementation } from \"effect-agent/sandbox\";\nimport {\n HttpClient,\n HttpClientRequest,\n type HttpClientError,\n type HttpClientResponse,\n} from \"effect/unstable/http\";\n\n/** Node-safe REST crawl implementation. It never exposes or persists provider job identity. */\nexport const browserRestCrawlImplementation = SandboxImplementation.make({\n isolation: \"isolated\",\n identity: \"cloudflare-browser-rest-crawl\",\n});\n\nconst API_ORIGIN = \"https://api.cloudflare.com\";\nconst POLL_INTERVAL = Duration.seconds(1);\nconst CANCEL_TIMEOUT = Duration.seconds(30);\nconst MAX_CONTROL_RESPONSE_BYTES = 64 * 1024;\nconst MAX_RESULTS_RESPONSE_BYTES = 12 * 1024 * 1024;\nconst MAX_DIAGNOSTIC_LENGTH = 8_000;\nconst MAX_CURSOR_LENGTH = 1_024;\nconst MAX_RECORDS_PER_RESPONSE = 10_000;\nconst BoundedJobId = Schema.NonEmptyString.check(Schema.isMaxLength(256));\nconst BoundedCursorString = Schema.NonEmptyString.check(Schema.isMaxLength(MAX_CURSOR_LENGTH));\n// The generated API response Schema says string; the current product example returns a number.\nconst ProviderCursor = Schema.Union([BoundedCursorString, Schema.Natural]);\n\nconst ProviderJobStatus = Schema.Literals([\n \"running\",\n \"completed\",\n \"errored\",\n \"cancelled_by_user\",\n \"cancelled_due_to_timeout\",\n \"cancelled_due_to_limits\",\n]);\n\nconst ProviderResult = Schema.Struct({\n id: BoundedJobId,\n status: ProviderJobStatus,\n browserSecondsUsed: Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0))),\n total: Schema.optionalKey(Schema.Natural),\n finished: Schema.optionalKey(Schema.Natural),\n skipped: Schema.optionalKey(Schema.Natural),\n records: Schema.Array(PageCrawlRecord).check(Schema.isMaxLength(MAX_RECORDS_PER_RESPONSE)),\n cursor: Schema.optionalKey(ProviderCursor),\n});\n\nconst CreateEnvelope = Schema.Struct({\n success: Schema.Literal(true),\n result: BoundedJobId,\n});\n\nconst ResultEnvelope = Schema.Struct({\n success: Schema.Literal(true),\n result: ProviderResult,\n});\n\nconst DeleteEnvelope = Schema.Struct({\n success: Schema.Literal(true),\n result: Schema.Struct({\n job_id: BoundedJobId,\n message: Schema.String.check(Schema.isMaxLength(MAX_DIAGNOSTIC_LENGTH)),\n }),\n});\n\n/** Explicit credentials. The token is projected only into fixed-origin Authorization headers. */\nexport interface BrowserRestCrawlOptions {\n readonly accountId: string;\n readonly apiToken: Redacted.Redacted<string>;\n}\n\nconst boundedDiagnostic = (message: string): string => message.slice(0, MAX_DIAGNOSTIC_LENGTH);\n\nconst privateCause = (value: unknown, apiToken: Redacted.Redacted<string>): Error | undefined => {\n const raw = boundedDiagnostic(String(value));\n\n if (raw.length === 0) return undefined;\n const token = Redacted.value(apiToken);\n\n return new Error(token.length === 0 ? raw : raw.replaceAll(token, \"[REDACTED]\"));\n};\n\nconst protocolError = (message: string, cause?: unknown): PageCrawlProtocolError =>\n PageCrawlProtocolError.make({\n implementation: browserRestCrawlImplementation,\n message: boundedDiagnostic(message),\n ...(cause === undefined ? {} : { cause }),\n });\n\nconst limitError = (\n request: PageCrawlRequest,\n limit: PageCrawlLimitError[\"limit\"],\n observed: number,\n): PageCrawlLimitError => {\n const maximum =\n limit === \"pages\"\n ? request.limits.maxPages\n : limit === \"page-bytes\"\n ? request.limits.maxPageBytes\n : limit === \"total-bytes\"\n ? request.limits.maxTotalBytes\n : request.limits.deadlineMillis;\n\n return PageCrawlLimitError.make({\n implementation: browserRestCrawlImplementation,\n limit,\n maximum,\n observed,\n message: `The crawl exceeded its ${limit} limit`,\n });\n};\n\nconst retryAfterMillis = (\n headers: Readonly<Record<string, string | undefined>>,\n): number | undefined => {\n const seconds = Number(headers[\"retry-after\"]);\n\n if (!Number.isSafeInteger(seconds) || seconds < 0) return undefined;\n const millis = seconds * 1_000;\n\n return Number.isSafeInteger(millis) ? millis : undefined;\n};\n\nconst isJsonResponse = (headers: Readonly<Record<string, string | undefined>>): boolean => {\n const contentType = headers[\"content-type\"];\n\n if (contentType === undefined) return false;\n const mediaType = contentType.split(\";\", 1)[0]?.trim().toLowerCase();\n\n return mediaType === \"application/json\" || mediaType?.endsWith(\"+json\") === true;\n};\n\nconst isQuotaMessage = (bodyText: string): boolean => /time limit|daily|quota/i.test(bodyText);\n\nconst endpoint = (options: BrowserRestCrawlOptions, jobId?: string): string =>\n `${API_ORIGIN}/client/v4/accounts/${encodeURIComponent(options.accountId)}/browser-rendering/crawl${\n jobId === undefined ? \"\" : `/${encodeURIComponent(jobId)}`\n }`;\n\nconst readBoundedResponse = Effect.fn(\"BrowserRestCrawl.readBoundedResponse\")(function* (\n response: HttpClientResponse.HttpClientResponse,\n maximum: number,\n): Effect.fn.Return<string, PageCrawlProtocolError> {\n const decoder = new TextDecoder(\"utf-8\", { fatal: true, ignoreBOM: false });\n\n const state = yield* Stream.runFoldEffect<\n Uint8Array,\n HttpClientError.HttpClientError,\n never,\n { readonly observed: number; readonly text: string },\n PageCrawlProtocolError,\n never\n >(\n response.stream,\n () => ({ observed: 0, text: \"\" }),\n (current, chunk) => {\n const observed = current.observed + chunk.byteLength;\n\n if (observed > maximum) {\n return Effect.fail(\n protocolError(\n `The Browser Run crawl response exceeded ${String(maximum)} transport bytes`,\n ),\n );\n }\n\n return Effect.try({\n try: () => ({ observed, text: current.text + decoder.decode(chunk, { stream: true }) }),\n catch: (cause) => protocolError(\"Decoding the Browser Run crawl response failed\", cause),\n });\n },\n ).pipe(\n Effect.mapError((cause) =>\n Schema.is(PageCrawlProtocolError)(cause)\n ? cause\n : protocolError(\"Reading the Browser Run crawl response failed\", cause),\n ),\n );\n\n return yield* Effect.try({\n try: () => state.text + decoder.decode(),\n catch: (cause) => protocolError(\"Decoding the Browser Run crawl response failed\", cause),\n });\n});\n\nconst deadlineFailure = Effect.fn(\"BrowserRestCrawl.deadlineFailure\")(function* (\n request: PageCrawlRequest,\n startedAt: number,\n) {\n const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);\n\n return yield* limitError(request, \"deadline\", Math.max(0, now - startedAt));\n});\n\nconst withinDeadline = Effect.fn(\"BrowserRestCrawl.withinDeadline\")(function* <A, E, R>(\n effect: Effect.Effect<A, E, R>,\n request: PageCrawlRequest,\n startedAt: number,\n): Effect.fn.Return<A, E | PageCrawlLimitError, R> {\n const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);\n const elapsed = Math.max(0, now - startedAt);\n const remaining = request.limits.deadlineMillis - elapsed;\n\n if (remaining <= 0) return yield* limitError(request, \"deadline\", elapsed);\n\n return yield* effect.pipe(\n Effect.timeoutOrElse({\n duration: Duration.millis(remaining),\n orElse: () => deadlineFailure(request, startedAt),\n }),\n );\n});\n\nconst checkDeadline = Effect.fn(\"BrowserRestCrawl.checkDeadline\")(function* (\n request: PageCrawlRequest,\n startedAt: number,\n) {\n const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);\n const elapsed = Math.max(0, now - startedAt);\n\n if (elapsed >= request.limits.deadlineMillis) {\n return yield* limitError(request, \"deadline\", elapsed);\n }\n});\n\nconst normalizedCursor = (cursor: typeof ProviderCursor.Type): string => String(cursor);\n\ninterface PaginationState {\n readonly cursor: Option.Option<string>;\n readonly seen: ReadonlyArray<string>;\n}\n\ninterface RecordLimitsState {\n readonly pages: number;\n readonly totalBytes: number;\n}\n\nconst sameHost = (expectedHost: string, url: string): boolean => new URL(url).host === expectedHost;\n\nconst makeCrawl =\n (client: HttpClient.HttpClient, options: BrowserRestCrawlOptions): PageCrawlCrawl =>\n (input) =>\n Stream.unwrap(\n Effect.gen(function* () {\n const startedAt = yield* Effect.clockWith((clock) => clock.currentTimeMillis);\n const startHost = new URL(input.startUrl).host;\n\n const executeJson = Effect.fn(\"BrowserRestCrawl.executeJson\")(function* <\n S extends Schema.Top,\n >(request: HttpClientRequest.HttpClientRequest, schema: S, maximum: number) {\n const authorized = HttpClientRequest.bearerToken(request, options.apiToken).pipe(\n HttpClientRequest.acceptJson,\n );\n\n const response = yield* client\n .execute(authorized)\n .pipe(\n Effect.mapError((cause) =>\n protocolError(\n \"Calling the Browser Run crawl REST API failed\",\n privateCause(cause, options.apiToken),\n ),\n ),\n );\n\n const bodyText = yield* readBoundedResponse(response, maximum);\n\n if (response.status === 429) {\n const reason = isQuotaMessage(bodyText) ? \"quota\" : \"rate\";\n\n return yield* PageCrawlRateLimitedError.make({\n implementation: browserRestCrawlImplementation,\n reason,\n ...(retryAfterMillis(response.headers) === undefined\n ? {}\n : { retryAfterMillis: retryAfterMillis(response.headers) }),\n message:\n reason === \"quota\"\n ? \"Browser Run exceeded its browser quota\"\n : \"Browser Run crawl was rate limited\",\n ...(privateCause(bodyText, options.apiToken) === undefined\n ? {}\n : { cause: privateCause(bodyText, options.apiToken) }),\n });\n }\n if (response.status < 200 || response.status >= 300) {\n return yield* protocolError(\n `Browser Run crawl answered HTTP ${String(response.status)}`,\n privateCause(bodyText, options.apiToken),\n );\n }\n if (!isJsonResponse(response.headers)) {\n return yield* protocolError(\n \"The Browser Run crawl success response was not JSON\",\n privateCause(bodyText, options.apiToken),\n );\n }\n\n return yield* Schema.decodeEffect(Schema.fromJsonString(schema))(bodyText).pipe(\n Effect.mapError((cause) =>\n protocolError(\n \"Browser Run returned a malformed crawl response\",\n privateCause(cause, options.apiToken),\n ),\n ),\n );\n });\n\n const createJob = Effect.gen(function* () {\n const requestWithBody = yield* HttpClientRequest.post(endpoint(options)).pipe(\n HttpClientRequest.bodyJson({\n url: input.startUrl,\n crawlPurposes: [...input.purposes],\n limit: input.limits.maxPages,\n depth: input.limits.maxDepth,\n formats: [\"markdown\"],\n render: true,\n options: {\n includeExternalLinks: false,\n includeSubdomains: false,\n },\n }),\n Effect.mapError((cause) =>\n protocolError(\"Encoding the Browser Run crawl request failed\", cause),\n ),\n );\n\n const created = yield* withinDeadline(\n executeJson(requestWithBody, CreateEnvelope, MAX_CONTROL_RESPONSE_BYTES),\n input,\n startedAt,\n );\n\n return created.result;\n });\n\n const job = yield* Effect.uninterruptibleMask((restore) =>\n restore(createJob).pipe(\n Effect.flatMap((id) =>\n Effect.gen(function* () {\n const state = yield* Ref.make<\"running\" | \"terminal\">(\"running\");\n const scope = yield* Effect.scope;\n\n const cancelIfRunning = Ref.modify(state, (current) =>\n current === \"running\"\n ? [true, \"terminal\" as const]\n : [false, \"terminal\" as const],\n ).pipe(\n Effect.flatMap((shouldCancel) => {\n if (!shouldCancel) return Effect.void;\n\n const cancellation = executeJson(\n HttpClientRequest.delete(endpoint(options, id)),\n DeleteEnvelope,\n MAX_CONTROL_RESPONSE_BYTES,\n ).pipe(\n Effect.flatMap((deleted) =>\n deleted.result.job_id === id\n ? Effect.void\n : protocolError(\"Browser Run cancelled a different crawl job\"),\n ),\n );\n\n return cancellation.pipe(\n Effect.timeoutOrElse({\n duration: CANCEL_TIMEOUT,\n orElse: () =>\n protocolError(\"Cancelling the Browser Run crawl exceeded 30 seconds\"),\n }),\n );\n }),\n );\n\n yield* Scope.addFinalizer(\n scope,\n cancelIfRunning.pipe(\n Effect.catchCause(() =>\n Effect.logWarning(\"Browser Run crawl cancellation failed\"),\n ),\n ),\n );\n\n return { id, state } as const;\n }),\n ),\n ),\n );\n\n const fetchResult = Effect.fn(\"BrowserRestCrawl.fetchResult\")(function* (\n cursor: Option.Option<string>,\n statusOnly: boolean,\n ) {\n let request = HttpClientRequest.get(endpoint(options, job.id));\n\n if (statusOnly) request = HttpClientRequest.setUrlParam(request, \"limit\", \"1\");\n if (Option.isSome(cursor)) {\n request = HttpClientRequest.setUrlParam(request, \"cursor\", cursor.value);\n }\n\n // `limit=1` still permits one complete record, so poll GETs use the bounded result cap.\n const envelope = yield* withinDeadline(\n executeJson(request, ResultEnvelope, MAX_RESULTS_RESPONSE_BYTES),\n input,\n startedAt,\n );\n\n if (envelope.result.id !== job.id) {\n return yield* protocolError(\"Browser Run returned a different crawl job identity\");\n }\n\n return envelope.result;\n });\n\n const terminal = yield* Effect.uninterruptibleMask((restore) =>\n restore(\n withinDeadline(\n Effect.repeat(fetchResult(Option.none(), true), {\n schedule: Schedule.spaced(POLL_INTERVAL),\n until: (result) => result.status !== \"running\",\n }),\n input,\n startedAt,\n ),\n ).pipe(Effect.tap(() => Ref.set(job.state, \"terminal\"))),\n );\n\n if (terminal.status === \"running\") {\n return yield* protocolError(\"Browser Run polling stopped before a terminal status\");\n }\n if (terminal.status !== \"completed\") {\n return yield* PageCrawlTerminalError.make({\n implementation: browserRestCrawlImplementation,\n status: terminal.status,\n message: `Browser Run crawl ended with status ${terminal.status}`,\n });\n }\n\n const pages = Stream.paginate<PaginationState, PageCrawlRecord, PageCrawlError>(\n { cursor: Option.none(), seen: [] },\n (state) =>\n Effect.gen(function* () {\n const result = yield* fetchResult(state.cursor, false);\n\n if (result.status !== \"completed\") {\n return yield* protocolError(\n \"Browser Run changed crawl status during result pagination\",\n );\n }\n if (result.cursor === undefined) {\n return [result.records, Option.none()] as const;\n }\n const cursor = normalizedCursor(result.cursor);\n\n if (state.seen.includes(cursor)) {\n return yield* protocolError(\"Browser Run repeated a crawl result cursor\");\n }\n if (state.seen.length >= input.limits.maxPages) {\n return yield* protocolError(\"Browser Run returned too many crawl result cursors\");\n }\n\n return [\n result.records,\n Option.some({ cursor: Option.some(cursor), seen: [...state.seen, cursor] }),\n ] as const;\n }),\n );\n\n return pages.pipe(\n Stream.rechunk(1),\n Stream.mapAccumEffect(\n (): RecordLimitsState => ({ pages: 0, totalBytes: 0 }),\n (state, record) =>\n Effect.gen(function* () {\n yield* checkDeadline(input, startedAt);\n const pages = state.pages + 1;\n\n if (pages > input.limits.maxPages) {\n return yield* limitError(input, \"pages\", pages);\n }\n if (!sameHost(startHost, record.url)) {\n return yield* protocolError(\"Browser Run returned an off-host crawl record\");\n }\n if (record.metadata !== undefined && !sameHost(startHost, record.metadata.url)) {\n return yield* protocolError(\"Browser Run returned off-host crawl metadata\");\n }\n\n const pageBytes =\n record.markdown === undefined\n ? 0\n : new TextEncoder().encode(record.markdown).byteLength;\n\n if (pageBytes > input.limits.maxPageBytes) {\n return yield* limitError(input, \"page-bytes\", pageBytes);\n }\n const totalBytes = state.totalBytes + pageBytes;\n\n if (totalBytes > input.limits.maxTotalBytes) {\n return yield* limitError(input, \"total-bytes\", totalBytes);\n }\n\n return [{ pages, totalBytes }, [record]] as const;\n }),\n ),\n );\n }),\n );\n\n/** Cloudflare REST PageCrawl Layer for Node and other non-Worker composition roots. */\nexport const browserRestCrawlLayer = (\n options: BrowserRestCrawlOptions,\n): Layer.Layer<PageCrawl, never, HttpClient.HttpClient> =>\n Layer.effect(\n PageCrawl,\n Effect.map(HttpClient.HttpClient, (client) =>\n PageCrawl.of({ crawl: makeCrawl(client, options) }),\n ),\n );\n"],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ import { Layer, Redacted } from "effect";
2
+ import { HttpClient } from "effect/unstable/http";
3
+ declare namespace CloudflareAiGateway_d_exports {
4
+ export { ClientOptions, ProviderOptions, RestOptions, RouteOptions, provide, provider, rest };
5
+ }
6
+ /** Options understood by upstream Effect AI clients; no model or provider wrapper is created. */
7
+ interface ClientOptions {
8
+ readonly apiUrl: string;
9
+ readonly transformClient: (client: HttpClient.HttpClient) => HttpClient.HttpClient;
10
+ }
11
+ interface ProviderOptions {
12
+ readonly accountId: string;
13
+ readonly gatewayId: string;
14
+ /** Cloudflare's provider path, such as openai, anthropic, google-ai-studio, or perplexity-ai. */
15
+ readonly provider: string;
16
+ /** Omit only for an unauthenticated gateway with a separately supplied provider key. */
17
+ readonly apiToken?: Redacted.Redacted<string>;
18
+ }
19
+ interface RestOptions {
20
+ readonly accountId: string;
21
+ readonly gatewayId: string;
22
+ /** Cloudflare API token with Workers AI Read permission. */
23
+ readonly apiToken: Redacted.Redacted<string>;
24
+ /** Matches the paths appended by the upstream Effect client. */
25
+ readonly protocol: "responses" | "chat-completions" | "messages";
26
+ }
27
+ /** Choose exactly one route: a native provider path or an account REST protocol. */
28
+ type RouteOptions = (ProviderOptions & {
29
+ readonly protocol?: never;
30
+ }) | (RestOptions & {
31
+ readonly provider?: never;
32
+ });
33
+ /**
34
+ * Provider-native proxy for model calls, streaming, embeddings, and hosted tools supported
35
+ * by that provider. Pass the result to the upstream client's layer along with its apiKey
36
+ * for BYOK-in-request, or omit apiKey for Gateway stored keys / Unified Billing.
37
+ * Provider model names and request bodies pass through unchanged. This module is Node-safe.
38
+ * Custom transforms and redirect policies must retain this endpoint and credential boundary.
39
+ */
40
+ declare const provider: (options: ProviderOptions) => ClientOptions;
41
+ /**
42
+ * Cloudflare's account REST API (not the deprecated /compat API). Use provider-qualified
43
+ * model names, e.g. openai/gpt-4.1 or anthropic/claude-haiku-4.5, and omit provider apiKey.
44
+ * OpenAI clients append /responses or /chat/completions; Anthropic appends /v1/messages.
45
+ * Compatibility remains the responsibility of the selected upstream client and model.
46
+ */
47
+ declare const rest: (options: RestOptions) => ClientOptions;
48
+ /**
49
+ * Provide a Gateway-configured upstream client directly in a Layer pipeline.
50
+ * Pass the client's `layer` factory, or a callback adding client-specific options.
51
+ * The factory's errors and remaining services (such as HttpClient) stay visible.
52
+ * Model selection and resource ownership remain with the supplied upstream Layers.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * AnthropicLanguageModel.model("claude-haiku-4-5").pipe(
57
+ * Gateway.provide(AnthropicClient.layer, { ...options, provider: "anthropic" }),
58
+ * )
59
+ * ```
60
+ */
61
+ declare const provide: <Client, E, R>(clientLayer: (options: ClientOptions) => Layer.Layer<Client, E, R>, options: RouteOptions) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, E | E2, R | Exclude<RIn2, Client>>;
62
+ //#endregion
63
+ export { ClientOptions, ProviderOptions, RestOptions, RouteOptions, provide, provider, rest, CloudflareAiGateway_d_exports as t };
64
+ //# sourceMappingURL=CloudflareAiGateway.d.mts.map
@@ -0,0 +1,70 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { Context, Effect, Layer, Option, Redactable, Redacted, Schema } from "effect";
3
+ import { FetchHttpClient, Headers, HttpClient, HttpClientError, HttpClientRequest } from "effect/unstable/http";
4
+ //#region src/CloudflareAiGateway.ts
5
+ var CloudflareAiGateway_exports = /* @__PURE__ */ __exportAll({
6
+ provide: () => provide,
7
+ provider: () => provider,
8
+ rest: () => rest
9
+ });
10
+ const Segment = Schema.NonEmptyString.check(Schema.isMaxLength(256), Schema.isPattern(/^[a-zA-Z0-9_-]+$/));
11
+ const decodeSegment = Schema.decodeUnknownSync(Segment);
12
+ const redactGatewayToken = (headers, tokenHeader) => {
13
+ Object.defineProperty(headers, Redactable.symbolRedactable, {
14
+ configurable: true,
15
+ value: (context) => Headers.redact(headers, [...Context.get(context, Headers.CurrentRedactedNames), tokenHeader])
16
+ });
17
+ };
18
+ const clientOptions = (apiUrl, tokenHeader, apiToken, gatewayId) => Object.freeze({
19
+ apiUrl,
20
+ transformClient: (client) => client.pipe(HttpClient.mapRequestEffect((request) => {
21
+ const url = URL.parse(request.url);
22
+ if (url === null || url.username !== "" || url.password !== "" || !(url.href === apiUrl || url.href.startsWith(`${apiUrl}/`))) return Effect.fail(new HttpClientError.HttpClientError({ reason: new HttpClientError.InvalidUrlError({
23
+ request,
24
+ description: "Request is outside the configured Cloudflare AI Gateway endpoint"
25
+ }) }));
26
+ let prepared = request;
27
+ if (apiToken !== void 0) prepared = HttpClientRequest.setHeader(prepared, tokenHeader, `Bearer ${Redacted.value(apiToken)}`);
28
+ if (gatewayId !== void 0) prepared = HttpClientRequest.setHeader(prepared, "cf-aig-gateway-id", gatewayId);
29
+ return Effect.succeed(prepared);
30
+ }), HttpClient.transformResponse((effect) => Effect.gen(function* () {
31
+ const defaults = yield* Effect.serviceOption(FetchHttpClient.RequestInit);
32
+ return yield* effect.pipe(Effect.provideService(FetchHttpClient.RequestInit, {
33
+ ...Option.getOrElse(defaults, () => ({})),
34
+ redirect: "error"
35
+ }));
36
+ })), HttpClient.transformResponse((effect) => effect.pipe(Effect.tap((response) => Effect.sync(() => redactGatewayToken(response.request.headers, tokenHeader))), Effect.tapError((error) => Effect.sync(() => redactGatewayToken(error.reason.request.headers, tokenHeader))), Effect.updateService(Headers.CurrentRedactedNames, (names) => [...names, tokenHeader]))))
37
+ });
38
+ /**
39
+ * Provider-native proxy for model calls, streaming, embeddings, and hosted tools supported
40
+ * by that provider. Pass the result to the upstream client's layer along with its apiKey
41
+ * for BYOK-in-request, or omit apiKey for Gateway stored keys / Unified Billing.
42
+ * Provider model names and request bodies pass through unchanged. This module is Node-safe.
43
+ * Custom transforms and redirect policies must retain this endpoint and credential boundary.
44
+ */
45
+ const provider = (options) => clientOptions(`https://gateway.ai.cloudflare.com/v1/${decodeSegment(options.accountId)}/${decodeSegment(options.gatewayId)}/${decodeSegment(options.provider)}`, "cf-aig-authorization", options.apiToken);
46
+ /**
47
+ * Cloudflare's account REST API (not the deprecated /compat API). Use provider-qualified
48
+ * model names, e.g. openai/gpt-4.1 or anthropic/claude-haiku-4.5, and omit provider apiKey.
49
+ * OpenAI clients append /responses or /chat/completions; Anthropic appends /v1/messages.
50
+ * Compatibility remains the responsibility of the selected upstream client and model.
51
+ */
52
+ const rest = (options) => clientOptions(`https://api.cloudflare.com/client/v4/accounts/${decodeSegment(options.accountId)}/ai${options.protocol === "messages" ? "" : "/v1"}`, "authorization", options.apiToken, decodeSegment(options.gatewayId));
53
+ /**
54
+ * Provide a Gateway-configured upstream client directly in a Layer pipeline.
55
+ * Pass the client's `layer` factory, or a callback adding client-specific options.
56
+ * The factory's errors and remaining services (such as HttpClient) stay visible.
57
+ * Model selection and resource ownership remain with the supplied upstream Layers.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * AnthropicLanguageModel.model("claude-haiku-4-5").pipe(
62
+ * Gateway.provide(AnthropicClient.layer, { ...options, provider: "anthropic" }),
63
+ * )
64
+ * ```
65
+ */
66
+ const provide = (clientLayer, options) => Layer.provide(clientLayer(options.provider !== void 0 ? provider(options) : rest(options)));
67
+ //#endregion
68
+ export { provide, provider, rest, CloudflareAiGateway_exports as t };
69
+
70
+ //# sourceMappingURL=CloudflareAiGateway.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CloudflareAiGateway.mjs","names":[],"sources":["../src/CloudflareAiGateway.ts"],"sourcesContent":["import { Context, Effect, Layer, Option, Redactable, Redacted, Schema } from \"effect\";\nimport {\n FetchHttpClient,\n Headers,\n HttpClient,\n HttpClientError,\n HttpClientRequest,\n} from \"effect/unstable/http\";\n\nconst Segment = Schema.NonEmptyString.check(\n Schema.isMaxLength(256),\n Schema.isPattern(/^[a-zA-Z0-9_-]+$/),\n);\n\nconst decodeSegment = Schema.decodeUnknownSync(Segment);\n\n/** Options understood by upstream Effect AI clients; no model or provider wrapper is created. */\nexport interface ClientOptions {\n readonly apiUrl: string;\n readonly transformClient: (client: HttpClient.HttpClient) => HttpClient.HttpClient;\n}\n\nexport interface ProviderOptions {\n readonly accountId: string;\n readonly gatewayId: string;\n /** Cloudflare's provider path, such as openai, anthropic, google-ai-studio, or perplexity-ai. */\n readonly provider: string;\n /** Omit only for an unauthenticated gateway with a separately supplied provider key. */\n readonly apiToken?: Redacted.Redacted<string>;\n}\n\nexport interface RestOptions {\n readonly accountId: string;\n readonly gatewayId: string;\n /** Cloudflare API token with Workers AI Read permission. */\n readonly apiToken: Redacted.Redacted<string>;\n /** Matches the paths appended by the upstream Effect client. */\n readonly protocol: \"responses\" | \"chat-completions\" | \"messages\";\n}\n\n/** Choose exactly one route: a native provider path or an account REST protocol. */\nexport type RouteOptions =\n | (ProviderOptions & { readonly protocol?: never })\n | (RestOptions & { readonly provider?: never });\n\nconst redactGatewayToken = (headers: Headers.Headers, tokenHeader: string): void => {\n // HTTP tracing can copy headers after preprocessing. Attach the public redaction\n // protocol to the final request as well, before a provider builds error details.\n Object.defineProperty(headers, Redactable.symbolRedactable, {\n configurable: true,\n value: (context: Context.Context<never>) =>\n Headers.redact(headers, [...Context.get(context, Headers.CurrentRedactedNames), tokenHeader]),\n });\n};\n\nconst clientOptions = (\n apiUrl: string,\n tokenHeader: \"authorization\" | \"cf-aig-authorization\",\n apiToken: Redacted.Redacted<string> | undefined,\n gatewayId?: string,\n): ClientOptions =>\n Object.freeze({\n apiUrl,\n transformClient: (client: HttpClient.HttpClient) =>\n client.pipe(\n HttpClient.mapRequestEffect((request) => {\n const url = URL.parse(request.url);\n\n // Check the normalized URL too: dot segments must not escape this account or gateway.\n if (\n url === null ||\n url.username !== \"\" ||\n url.password !== \"\" ||\n !(url.href === apiUrl || url.href.startsWith(`${apiUrl}/`))\n ) {\n return Effect.fail(\n new HttpClientError.HttpClientError({\n reason: new HttpClientError.InvalidUrlError({\n request,\n description: \"Request is outside the configured Cloudflare AI Gateway endpoint\",\n }),\n }),\n );\n }\n let prepared = request;\n\n if (apiToken !== undefined) {\n prepared = HttpClientRequest.setHeader(\n prepared,\n tokenHeader,\n `Bearer ${Redacted.value(apiToken)}`,\n );\n }\n if (gatewayId !== undefined) {\n prepared = HttpClientRequest.setHeader(prepared, \"cf-aig-gateway-id\", gatewayId);\n }\n\n return Effect.succeed(prepared);\n }),\n HttpClient.transformResponse((effect) =>\n Effect.gen(function* () {\n const defaults = yield* Effect.serviceOption(FetchHttpClient.RequestInit);\n\n return yield* effect.pipe(\n Effect.provideService(FetchHttpClient.RequestInit, {\n ...Option.getOrElse(defaults, () => ({})),\n redirect: \"error\",\n }),\n );\n }),\n ),\n HttpClient.transformResponse((effect) =>\n effect.pipe(\n Effect.tap((response) =>\n Effect.sync(() => redactGatewayToken(response.request.headers, tokenHeader)),\n ),\n Effect.tapError((error) =>\n Effect.sync(() => redactGatewayToken(error.reason.request.headers, tokenHeader)),\n ),\n Effect.updateService(Headers.CurrentRedactedNames, (names) => [...names, tokenHeader]),\n ),\n ),\n ),\n });\n\n/**\n * Provider-native proxy for model calls, streaming, embeddings, and hosted tools supported\n * by that provider. Pass the result to the upstream client's layer along with its apiKey\n * for BYOK-in-request, or omit apiKey for Gateway stored keys / Unified Billing.\n * Provider model names and request bodies pass through unchanged. This module is Node-safe.\n * Custom transforms and redirect policies must retain this endpoint and credential boundary.\n */\nexport const provider = (options: ProviderOptions): ClientOptions =>\n clientOptions(\n `https://gateway.ai.cloudflare.com/v1/${decodeSegment(options.accountId)}/${decodeSegment(options.gatewayId)}/${decodeSegment(options.provider)}`,\n \"cf-aig-authorization\",\n options.apiToken,\n );\n\n/**\n * Cloudflare's account REST API (not the deprecated /compat API). Use provider-qualified\n * model names, e.g. openai/gpt-4.1 or anthropic/claude-haiku-4.5, and omit provider apiKey.\n * OpenAI clients append /responses or /chat/completions; Anthropic appends /v1/messages.\n * Compatibility remains the responsibility of the selected upstream client and model.\n */\nexport const rest = (options: RestOptions): ClientOptions =>\n clientOptions(\n `https://api.cloudflare.com/client/v4/accounts/${decodeSegment(options.accountId)}/ai${options.protocol === \"messages\" ? \"\" : \"/v1\"}`,\n \"authorization\",\n options.apiToken,\n decodeSegment(options.gatewayId),\n );\n\n/**\n * Provide a Gateway-configured upstream client directly in a Layer pipeline.\n * Pass the client's `layer` factory, or a callback adding client-specific options.\n * The factory's errors and remaining services (such as HttpClient) stay visible.\n * Model selection and resource ownership remain with the supplied upstream Layers.\n *\n * @example\n * ```ts\n * AnthropicLanguageModel.model(\"claude-haiku-4-5\").pipe(\n * Gateway.provide(AnthropicClient.layer, { ...options, provider: \"anthropic\" }),\n * )\n * ```\n */\nexport const provide = <Client, E, R>(\n clientLayer: (options: ClientOptions) => Layer.Layer<Client, E, R>,\n options: RouteOptions,\n) => Layer.provide(clientLayer(options.provider !== undefined ? provider(options) : rest(options)));\n"],"mappings":";;;;;;;;;AASA,MAAM,UAAU,OAAO,eAAe,MACpC,OAAO,YAAY,GAAG,GACtB,OAAO,UAAU,kBAAkB,CACrC;AAEA,MAAM,gBAAgB,OAAO,kBAAkB,OAAO;AA+BtD,MAAM,sBAAsB,SAA0B,gBAA8B;CAGlF,OAAO,eAAe,SAAS,WAAW,kBAAkB;EAC1D,cAAc;EACd,QAAQ,YACN,QAAQ,OAAO,SAAS,CAAC,GAAG,QAAQ,IAAI,SAAS,QAAQ,oBAAoB,GAAG,WAAW,CAAC;CAChG,CAAC;AACH;AAEA,MAAM,iBACJ,QACA,aACA,UACA,cAEA,OAAO,OAAO;CACZ;CACA,kBAAkB,WAChB,OAAO,KACL,WAAW,kBAAkB,YAAY;EACvC,MAAM,MAAM,IAAI,MAAM,QAAQ,GAAG;EAGjC,IACE,QAAQ,QACR,IAAI,aAAa,MACjB,IAAI,aAAa,MACjB,EAAE,IAAI,SAAS,UAAU,IAAI,KAAK,WAAW,GAAG,OAAO,EAAE,IAEzD,OAAO,OAAO,KACZ,IAAI,gBAAgB,gBAAgB,EAClC,QAAQ,IAAI,gBAAgB,gBAAgB;GAC1C;GACA,aAAa;EACf,CAAC,EACH,CAAC,CACH;EAEF,IAAI,WAAW;EAEf,IAAI,aAAa,KAAA,GACf,WAAW,kBAAkB,UAC3B,UACA,aACA,UAAU,SAAS,MAAM,QAAQ,GACnC;EAEF,IAAI,cAAc,KAAA,GAChB,WAAW,kBAAkB,UAAU,UAAU,qBAAqB,SAAS;EAGjF,OAAO,OAAO,QAAQ,QAAQ;CAChC,CAAC,GACD,WAAW,mBAAmB,WAC5B,OAAO,IAAI,aAAa;EACtB,MAAM,WAAW,OAAO,OAAO,cAAc,gBAAgB,WAAW;EAExE,OAAO,OAAO,OAAO,KACnB,OAAO,eAAe,gBAAgB,aAAa;GACjD,GAAG,OAAO,UAAU,iBAAiB,CAAC,EAAE;GACxC,UAAU;EACZ,CAAC,CACH;CACF,CAAC,CACH,GACA,WAAW,mBAAmB,WAC5B,OAAO,KACL,OAAO,KAAK,aACV,OAAO,WAAW,mBAAmB,SAAS,QAAQ,SAAS,WAAW,CAAC,CAC7E,GACA,OAAO,UAAU,UACf,OAAO,WAAW,mBAAmB,MAAM,OAAO,QAAQ,SAAS,WAAW,CAAC,CACjF,GACA,OAAO,cAAc,QAAQ,uBAAuB,UAAU,CAAC,GAAG,OAAO,WAAW,CAAC,CACvF,CACF,CACF;AACJ,CAAC;;;;;;;;AASH,MAAa,YAAY,YACvB,cACE,wCAAwC,cAAc,QAAQ,SAAS,EAAE,GAAG,cAAc,QAAQ,SAAS,EAAE,GAAG,cAAc,QAAQ,QAAQ,KAC9I,wBACA,QAAQ,QACV;;;;;;;AAQF,MAAa,QAAQ,YACnB,cACE,iDAAiD,cAAc,QAAQ,SAAS,EAAE,KAAK,QAAQ,aAAa,aAAa,KAAK,SAC9H,iBACA,QAAQ,UACR,cAAc,QAAQ,SAAS,CACjC;;;;;;;;;;;;;;AAeF,MAAa,WACX,aACA,YACG,MAAM,QAAQ,YAAY,QAAQ,aAAa,KAAA,IAAY,SAAS,OAAO,IAAI,KAAK,OAAO,CAAC,CAAC"}
@@ -0,0 +1,115 @@
1
+ import { Context, Effect, Layer, Schema } from "effect";
2
+ import { ThreadId } from "effect-agent/identifiers";
3
+ import { ProducerId } from "effect-agent/records";
4
+ declare namespace CloudflareBindings_d_exports {
5
+ export { CloudflareBindingError, DurableObjectContext, ThreadObjectClient, ThreadObjectIdentity, ThreadObjectNamespace, ThreadObjectPlacement, ThreadObjectRpc, callThreadObject, threadNamespaceFromEnv, threadNamespaceLayer };
6
+ }
7
+ declare const CloudflareBindingError_base: Schema.Class<CloudflareBindingError, Schema.TaggedStruct<"CloudflareBindingError", {
8
+ readonly binding: Schema.String;
9
+ readonly message: Schema.String;
10
+ }>, import("effect/Cause").YieldableError>;
11
+ /**
12
+ * Cloudflare platform bindings as Effect services (DEPLOY-010: "Cloudflare platform bindings
13
+ * are supplied as Effect services/Layers"). Application code never reads `env` or touches a
14
+ * `DurableObjectState` directly — the Thread Object class constructs these Layers once
15
+ * per incarnation and everything downstream consumes the services.
16
+ */
17
+ /** A Cloudflare platform binding was missing or carried the wrong shape (DEPLOY-003/010). */
18
+ declare class CloudflareBindingError extends CloudflareBindingError_base {}
19
+ /**
20
+ * The RPC surface one Thread Durable Object exposes to Workers and to sibling
21
+ * Thread Objects. `ThreadObject.make` implements it; the Worker-side client
22
+ * and the cross-Object transport call it through `DurableObjectNamespace` stubs. Every
23
+ * `encoded` value is a Schema-encoded envelope (`client.ts` wire schemas for host entry
24
+ * points, `@effect-agent/storage-cloudflare` port envelopes for `portCall`), so the RPC
25
+ * boundary carries only structured-cloneable JSON. The optional trailing trace context is
26
+ * transient native RPC metadata, stripped by an opted-in effect-cf receiver before decoding
27
+ * the host envelope. It never enters durable state.
28
+ */
29
+ interface ThreadObjectRpc extends Rpc.DurableObjectBranded {
30
+ /** Admission-limits gate + `DurableAgentRuntime.submit`; answers a `SubmitResponse`. */
31
+ submitEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
32
+ /** Wake-hinted, poll-guaranteed settlement wait; answers an `AwaitSettlementResponse`. */
33
+ submissionStatusEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
34
+ awaitSettlementEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
35
+ /** Event-driven durable progress wait; answers a `ProgressObserved` host response. */
36
+ awaitProgressEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
37
+ /** Best-effort cancellation for one in-flight progress wait. */
38
+ cancelProgressEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
39
+ /** One bounded page of canonical records; answers an `ObservePageResponse`. */
40
+ observePage(encoded: unknown, traceContext?: unknown): Promise<unknown>;
41
+ /** Durable abort intent; answers an `AbortResponse`. */
42
+ abortEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
43
+ /** Durable approval decision (plan §2.6); answers a `ResolveApprovalResponse`. */
44
+ resolveApprovalEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
45
+ /** Authorized DUR-017 Unknown-Outcome resolution; answers a `ResolveUnknownResponse`. */
46
+ resolveUnknownEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
47
+ /** Owner-side cross-Object port endpoint (WP2 envelopes, executed on LOCAL facets). */
48
+ portCall(encoded: unknown): Promise<unknown>;
49
+ /** Droppable liveness hint from another Object: arms an immediate alarm. */
50
+ wake(): Promise<void>;
51
+ }
52
+ /** A logical Thread endpoint may be bound to an application Object without forging its brand. */
53
+ type ThreadObjectClient = Omit<ThreadObjectRpc, keyof Rpc.DurableObjectBranded>;
54
+ declare const ThreadObjectNamespace_base: Context.ServiceClass<ThreadObjectNamespace, "@effect-agent/platform-cloudflare/ThreadObjectNamespace", {
55
+ readonly get: (threadId: ThreadId) => ThreadObjectClient;
56
+ /** Stable binding name for opted-in native RPC tracing; absent by default. */
57
+ readonly rpcTracing?: string;
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
+ declare class ThreadObjectNamespace extends ThreadObjectNamespace_base {
66
+ static layer(namespace: DurableObjectNamespace<ThreadObjectRpc>, options?: {
67
+ readonly rpcTracing?: string;
68
+ }): Layer.Layer<ThreadObjectNamespace>;
69
+ }
70
+ /** Invoke a placed Thread using the current invocation's native RPC channel. */
71
+ declare const callThreadObject: <A, E>(threadId: string & import("effect/Brand").Brand<"@effect-agent/core/ThreadId">, invoke: (target: ThreadObjectClient) => Promise<A>, onError: (cause: unknown) => E) => Effect.Effect<A, E, ThreadObjectNamespace>;
72
+ /**
73
+ * Narrow one `env` member to a `DurableObjectNamespace`. `env` is an untyped platform value,
74
+ * and a namespace binding is a host object no Schema can decode, so this is the documented
75
+ * narrowest-boundary check (structural probe for the namespace surface the transport uses);
76
+ * a missing or misshaped binding fails typed before any Layer is built.
77
+ */
78
+ declare const threadNamespaceFromEnv: (env: unknown, binding: string) => Effect.Effect<DurableObjectNamespace<ThreadObjectRpc>, CloudflareBindingError, never>;
79
+ /**
80
+ * Build the namespace from Worker `env` (fails typed). Enable `rpcTracing` only when the
81
+ * receiver also opts into the effect-cf native RPC trace-context contract.
82
+ */
83
+ declare const threadNamespaceLayer: (env: unknown, binding: string, options?: {
84
+ readonly rpcTracing?: boolean;
85
+ }) => Layer.Layer<ThreadObjectNamespace, CloudflareBindingError>;
86
+ declare const DurableObjectContext_base: Context.ServiceClass<DurableObjectContext, "@effect-agent/platform-cloudflare/DurableObjectContext", {
87
+ readonly ctx: DurableObjectState;
88
+ readonly env: unknown;
89
+ }>;
90
+ /**
91
+ * The live Durable Object execution context of THIS incarnation. Only Layer construction and
92
+ * the alarm service consume it; important state never lives on it (`ctx.storage` is truth,
93
+ * everything in memory is a cache — deployment spec §11).
94
+ */
95
+ declare class DurableObjectContext extends DurableObjectContext_base {
96
+ static layer(ctx: DurableObjectState, env: unknown): Layer.Layer<DurableObjectContext>;
97
+ }
98
+ declare const ThreadObjectIdentity_base: Context.ServiceClass<ThreadObjectIdentity, "@effect-agent/platform-cloudflare/ThreadObjectIdentity", {
99
+ readonly threadId: ThreadId;
100
+ readonly producerId: ProducerId;
101
+ }>;
102
+ /**
103
+ * The Thread identity this Object serializes and the producer identity its Attempts
104
+ * write with (`{producerPrefix}:{threadId}`, plan §1.4). Derived once per incarnation
105
+ * from `ctx.id.name` — the Object identity rule guarantees the name IS the Thread ID.
106
+ */
107
+ declare class ThreadObjectIdentity extends ThreadObjectIdentity_base {}
108
+ declare const ThreadObjectPlacement_base: Context.ServiceClass<ThreadObjectPlacement, "@effect-agent/platform-cloudflare/ThreadObjectPlacement", {
109
+ readonly ownsThread: (threadId: ThreadId) => boolean;
110
+ }>;
111
+ /** Logical Threads whose canonical stores and admission ledger live in this physical Object. */
112
+ declare class ThreadObjectPlacement extends ThreadObjectPlacement_base {}
113
+ //#endregion
114
+ export { CloudflareBindingError, DurableObjectContext, ThreadObjectClient, ThreadObjectIdentity, ThreadObjectNamespace, ThreadObjectPlacement, ThreadObjectRpc, callThreadObject, CloudflareBindings_d_exports as t, threadNamespaceFromEnv, threadNamespaceLayer };
115
+ //# sourceMappingURL=CloudflareBindings.d.mts.map