diff --git a/.dev.vars.example b/.dev.vars.example new file mode 100644 index 0000000..0eb49c9 --- /dev/null +++ b/.dev.vars.example @@ -0,0 +1,27 @@ +# Local-only Cloudflare Worker bindings. Copy to .dev.vars and replace each value. +# Secrets must never be committed. +# AUTH_KV, FOLDER_CACHE, and MPU are configured bindings in wrangler.jsonc, not .dev.vars values. + +# S3 Signature V4 credentials held by CLI clients or your BFF. +ACCESS_KEY=replace-with-access-key-id +SECRET_KEY=replace-with-long-random-secret +REGION=auto + +# Google OAuth credentials with Drive API access. +GOOGLE_CLIENT_ID=replace-with-google-oauth-client-id +GOOGLE_CLIENT_SECRET=replace-with-google-oauth-client-secret +GOOGLE_REFRESH_TOKEN=replace-with-google-refresh-token + +# Comma-separated, exact bucket names. Unset denies every bucket. +ALLOWED_BUCKETS=assets +# Optional comma-separated subset of ALLOWED_BUCKETS that permits unsigned GET/HEAD. +PUBLIC_READ_BUCKETS= + +# Durable Object multipart uploads and ETag result behavior. +ALLOW_MULTIPART=true +ETAG_STYLE=md5 + +# Browser CORS: comma-separated exact origins or *. Leave empty to disable CORS. +CORS_ALLOWED_ORIGINS=http://localhost:5173 +# Set false to disable /docs and /openapi.yaml. +ENABLE_DOCS=true diff --git a/README.md b/README.md index 2fe24f9..3603d6d 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,10 @@ I.R.I.S. (Integrated Reliable Interop Storage) Use Cloudflare Workers to turn your Google Drive into S3 object storage at no extra cost. +## API documentation + +With `ENABLE_DOCS` left enabled (the default), each deployment serves an interactive API reference at `/docs` and the raw OpenAPI 3.1 document at `/openapi.yaml`. Frontend-specific setup, authentication details, limitations, and runnable examples live in [`docs/`](./docs/README.md). + ## About This is a Workers script that converts the Google Drive API into an S3-compatible API. Turn your Google Drive into object storage at no extra cost! @@ -74,6 +78,8 @@ https://developers.cloudflare.com/workers/configuration/secrets/#via-the-dashboa | `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `GOOGLE_REFRESH_TOKEN` | Google API credentials obtained from rclone. | | `ALLOWED_BUCKETS` | Set the buckets allowed, separated by `,`. A directory with the bucket name will be created directly under Google Drive. | | `PUBLIC_READ_BUCKETS` | *(Optional)* Buckets that allow unauthenticated GET/HEAD access without signature, separated by `,`. Write operations (PUT/POST/DELETE) still require authentication. Must be a subset of `ALLOWED_BUCKETS`. | +| `CORS_ALLOWED_ORIGINS` | *(Optional)* Comma-separated exact browser origins, or `*`. Unset emits no CORS headers. | +| `ENABLE_DOCS` | *(Optional)* Set to `false` to disable `/docs` and `/openapi.yaml`; enabled by default. | ### 4. Enable Multipart Uploads @@ -95,5 +101,12 @@ Google Drive's free tier has 15 GB total storage, and Google applies a 750 GB da ### 5. CORS Configuration -If you need to configure CORS, set up your own domain for Workers and use Cloudflare's Response Header Transform Rules to add the necessary headers. -https://developers.cloudflare.com/rules/transform/response-header-modification/ +I.R.I.S. provides native, deny-by-default CORS handling. Set `CORS_ALLOWED_ORIGINS` to a comma-separated list of exact origins: + +```ini +CORS_ALLOWED_ORIGINS=https://app.example.com,http://localhost:5173 +``` + +Use `*` only for a public, credential-free integration. Leave the value unset to emit no CORS headers, preserving CLI-only behavior. Allowed origins receive preflight support for S3 methods and the browser-readable response headers `ETag`, `Content-Range`, `Content-Length`, `Last-Modified`, `Accept-Ranges`, and `x-amz-request-id`. Server-to-server clients do not need CORS: requests without an `Origin` retain their normal S3 response and receive no `Access-Control-*` headers. Exact origin allow-lists add the harmless `Vary: Origin` response header for cache correctness. + +For browser uploads, keep `SECRET_KEY` in a BFF and give the browser short-lived presigned URLs. See the [frontend integration guide](./docs/integration-guide.md). diff --git a/biome.json b/biome.json index 653f73c..c48d2de 100644 --- a/biome.json +++ b/biome.json @@ -7,7 +7,7 @@ }, "files": { "ignoreUnknown": true, - "includes": ["**", "!node_modules", "!.next", "!dist", "!build", "!src/app/globals.css"] + "includes": ["**", "!node_modules", "!.next", "!dist", "!build", "!src/app/globals.css", "!worker-configuration.d.ts"] }, "formatter": { "lineWidth": 320, diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..a89e4e5 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,29 @@ +# I.R.I.S. frontend integration docs + +I.R.I.S. exposes a focused S3-compatible API on top of Google Drive. The live API reference is available from every enabled Worker deployment at [`/docs`](/docs), with the source OpenAPI document at [`/openapi.yaml`](/openapi.yaml). + +## Start here + +1. Configure the Worker with `CORS_ALLOWED_ORIGINS` for every browser origin that will upload or download objects. +2. Keep `SECRET_KEY` in a backend-for-frontend (BFF), not in browser code. +3. Let the BFF issue short-lived presigned URLs for a constrained bucket/key/method. +4. Upload or download through the presigned URL from the browser. + +| Document | Purpose | +| --- | --- | +| [Integration guide](./integration-guide.md) | Browser upload, multipart upload, listing, CORS, and recommended architecture. | +| [Authentication](./authentication.md) | The precise AWS Signature V4 behavior enforced by I.R.I.S. | +| [Limitations](./limitations.md) | Unsupported S3 features and Google Drive operational limits. | +| [`examples/bff-presign.ts`](./examples/bff-presign.ts) | A Workers BFF endpoint that creates short-lived PUT URLs. | +| [`examples/browser-upload.ts`](./examples/browser-upload.ts) | Single object upload from a browser. | +| [`examples/browser-multipart.ts`](./examples/browser-multipart.ts) | Strictly sequential browser multipart upload. | + +## Browser security model + +```text +Browser ── POST /presign ──> BFF (holds SECRET_KEY) + │ │ + └──── presigned S3 request ────┴──> I.R.I.S. Worker ──> Google Drive +``` + +The browser never receives `SECRET_KEY`. Presigned URLs must be short lived and scoped to the single object operation the browser needs. diff --git a/docs/authentication.md b/docs/authentication.md new file mode 100644 index 0000000..046fb1d --- /dev/null +++ b/docs/authentication.md @@ -0,0 +1,39 @@ +# Authentication and Signature V4 + +I.R.I.S. accepts AWS Signature Version 4 in either form: + +- an `Authorization: AWS4-HMAC-SHA256 …` header with `x-amz-date`; or +- a presigned request with `X-Amz-Algorithm`, `X-Amz-Credential`, `X-Amz-Date`, `X-Amz-SignedHeaders`, and `X-Amz-Signature` query parameters. + +The `Credential` access-key ID must equal the Worker's `ACCESS_KEY`; the signing key is derived from `SECRET_KEY`, `REGION`, service `s3`, and the `YYYYMMDD` request date. + +## Presigned URL expiry + +Query-authenticated requests require `X-Amz-Expires` from 1 through 604800 seconds (seven days). The Worker returns `403 AccessDenied` with `Request has expired` when it is missing, invalid, out of range, or its signed timestamp plus expiry is in the past. + +BFFs should normally use a much shorter period such as five minutes. + +## Header-authenticated requests + +The Worker requires a valid `x-amz-date` and rejects a request when the server time differs from it by more than 15 minutes. This returns `403 RequestTimeTooSkewed`. + +## Canonical request behavior + +The canonical request is built from: + +1. method; +2. URL path; +3. sorted query string (excluding `X-Amz-Signature` for presigned URLs); +4. the signed-header list and normalized values; +5. the signed-header list; and +6. `x-amz-content-sha256`, defaulting to `UNSIGNED-PAYLOAD`. + +I.R.I.S. deliberately canonicalizes a signed `accept-encoding` header to `identity`. Cloudflare can rewrite the received value at the edge; S3 SDKs that sign this header use `identity` for this reason. Browser code must not sign `accept-encoding` because browser networking controls it. + +Payload hashes are **not** verified. Browser and BFF clients should use `x-amz-content-sha256: UNSIGNED-PAYLOAD`; this is a deliberate streaming limitation, not an integrity guarantee. + +## Public-read buckets + +Buckets listed in `PUBLIC_READ_BUCKETS` permit unsigned `GET` and `HEAD` requests. All write operations still require valid Signature V4 authentication. `PUBLIC_READ_BUCKETS` must be a subset of `ALLOWED_BUCKETS`. + +For a tested signing reference, see the `signed()` helper in [`test/s3.test.ts`](../test/s3.test.ts). diff --git a/docs/examples/bff-presign.ts b/docs/examples/bff-presign.ts new file mode 100644 index 0000000..9a27134 --- /dev/null +++ b/docs/examples/bff-presign.ts @@ -0,0 +1,95 @@ +import { AbortMultipartUploadCommand, CompleteMultipartUploadCommand, CreateMultipartUploadCommand, PutObjectCommand, S3Client, UploadPartCommand } from "@aws-sdk/client-s3"; +import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; + +interface Env { + IRIS_ENDPOINT: string; + IRIS_ACCESS_KEY: string; + IRIS_SECRET_KEY: string; + IRIS_REGION: string; + IRIS_BUCKET: string; +} + +interface PresignRequest { + key: string; + contentType?: string; + operation?: "put" | "createMultipart" | "uploadPart" | "completeMultipart" | "abortMultipart"; + uploadId?: string; + partNumber?: number; + parts?: Array<{ ETag: string; PartNumber: number }>; +} + +function escapeXml(value: string): string { + return value.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); +} + +function completionXml(parts: Array<{ ETag: string; PartNumber: number }> | undefined): string | null { + if (!parts?.length || parts.some((part, index) => !Number.isInteger(part.PartNumber) || part.PartNumber !== index + 1 || typeof part.ETag !== "string" || part.ETag.length === 0)) return null; + return `${parts.map((part) => `${part.PartNumber}${escapeXml(part.ETag)}`).join("")}`; +} + +function json(data: unknown, status = 200): Response { + return Response.json(data, { status, headers: { "Cache-Control": "no-store" } }); +} + +function s3(env: Env): S3Client { + return new S3Client({ + endpoint: env.IRIS_ENDPOINT, + region: env.IRIS_REGION, + forcePathStyle: true, + credentials: { accessKeyId: env.IRIS_ACCESS_KEY, secretAccessKey: env.IRIS_SECRET_KEY }, + }); +} + +function validKey(key: unknown): key is string { + return typeof key === "string" && key.length > 0 && key.length <= 1024 && !key.startsWith("/") && !key.split("/").includes(".."); +} + +export default { + async fetch(request: Request, env: Env): Promise { + if (request.method !== "POST" || new URL(request.url).pathname !== "/presign") return new Response("Not found", { status: 404 }); + + let input: PresignRequest; + try { + input = await request.json(); + } catch { + return json({ error: "Expected JSON body" }, 400); + } + if (!validKey(input.key)) return json({ error: "Invalid object key" }, 400); + + const client = s3(env); + const operation = input.operation ?? "put"; + const expiresIn = 5 * 60; + + if (operation === "put") { + const contentType = input.contentType || "application/octet-stream"; + const url = await getSignedUrl(client, new PutObjectCommand({ Bucket: env.IRIS_BUCKET, Key: input.key, ContentType: contentType }), { expiresIn }); + return json({ url, method: "PUT", headers: { "Content-Type": contentType }, expiresIn }); + } + + if (operation === "createMultipart") { + const contentType = input.contentType || "application/octet-stream"; + const url = await getSignedUrl(client, new CreateMultipartUploadCommand({ Bucket: env.IRIS_BUCKET, Key: input.key, ContentType: contentType }), { expiresIn }); + return json({ url, method: "POST", headers: { "Content-Type": contentType }, expiresIn }); + } + + if (!input.uploadId) return json({ error: "uploadId is required" }, 400); + if (operation === "uploadPart") { + const partNumber = input.partNumber; + if (!Number.isInteger(partNumber) || partNumber === undefined || partNumber < 1 || partNumber > 10_000) return json({ error: "partNumber must be 1 through 10000" }, 400); + const url = await getSignedUrl(client, new UploadPartCommand({ Bucket: env.IRIS_BUCKET, Key: input.key, UploadId: input.uploadId, PartNumber: partNumber }), { expiresIn }); + return json({ url, method: "PUT", expiresIn }); + } + if (operation === "completeMultipart") { + const body = completionXml(input.parts); + if (!body) return json({ error: "parts must have sequential PartNumber values beginning at 1 and non-empty ETag values" }, 400); + const url = await getSignedUrl(client, new CompleteMultipartUploadCommand({ Bucket: env.IRIS_BUCKET, Key: input.key, UploadId: input.uploadId, MultipartUpload: { Parts: input.parts } }), { expiresIn }); + return json({ url, method: "POST", headers: { "Content-Type": "application/xml" }, body, expiresIn }); + } + if (operation === "abortMultipart") { + const url = await getSignedUrl(client, new AbortMultipartUploadCommand({ Bucket: env.IRIS_BUCKET, Key: input.key, UploadId: input.uploadId }), { expiresIn }); + return json({ url, method: "DELETE", expiresIn }); + } + + return json({ error: "Unsupported operation" }, 400); + }, +} satisfies ExportedHandler; diff --git a/docs/examples/browser-multipart.ts b/docs/examples/browser-multipart.ts new file mode 100644 index 0000000..56ff2f0 --- /dev/null +++ b/docs/examples/browser-multipart.ts @@ -0,0 +1,48 @@ +interface PresignResponse { + url: string; + method: string; + headers?: Record; + body?: string; +} + +async function presign(body: Record): Promise { + const response = await fetch("/presign", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + if (!response.ok) throw new Error(await response.text()); + return response.json(); +} + +function uploadIdFromXml(xml: string): string { + const value = /([^<]+)<\/UploadId>/.exec(xml)?.[1]; + if (!value) throw new Error("CreateMultipartUpload did not return UploadId"); + return value; +} + +/** Uploads parts in order. I.R.I.S. does not allow parallel or out-of-order part uploads. */ +export async function uploadLargeFile(file: File, key: string, partSize = 16 * 1024 * 1024): Promise { + const contentType = file.type || "application/octet-stream"; + const create = await presign({ key, contentType, operation: "createMultipart" }); + const createResponse = await fetch(create.url, { method: create.method, headers: create.headers }); + if (!createResponse.ok) throw new Error(await createResponse.text()); + const uploadId = uploadIdFromXml(await createResponse.text()); + + const parts: Array<{ ETag: string; PartNumber: number }> = []; + for (let index = 0, offset = 0; offset < file.size; index++, offset += partSize) { + const partNumber = index + 1; + const body = file.slice(offset, Math.min(offset + partSize, file.size)); + const signed = await presign({ key, uploadId, partNumber, operation: "uploadPart" }); + const response = await fetch(signed.url, { method: signed.method, body }); + if (!response.ok) throw new Error(`Part ${partNumber} failed: ${await response.text()}`); + const etag = response.headers.get("ETag"); + if (!etag) throw new Error(`Part ${partNumber} did not return ETag; check CORS_ALLOWED_ORIGINS`); + parts.push({ ETag: etag, PartNumber: partNumber }); + } + + const complete = await presign({ key, uploadId, parts, operation: "completeMultipart" }); + if (!complete.body) throw new Error("BFF did not return the completion XML body"); + const completeResponse = await fetch(complete.url, { method: complete.method, headers: complete.headers, body: complete.body }); + if (!completeResponse.ok) throw new Error(await completeResponse.text()); +} diff --git a/docs/examples/browser-upload.ts b/docs/examples/browser-upload.ts new file mode 100644 index 0000000..3e5edfe --- /dev/null +++ b/docs/examples/browser-upload.ts @@ -0,0 +1,29 @@ +interface PresignedPut { + url: string; + method: "PUT"; + headers: { "Content-Type": string }; + expiresIn: number; +} + +async function getPresignedPut(key: string, contentType: string): Promise { + const response = await fetch("/presign", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ key, contentType, operation: "put" }), + }); + if (!response.ok) throw new Error(await response.text()); + return response.json(); +} + +export async function uploadFile(file: File, key: string): Promise { + const signed = await getPresignedPut(key, file.type || "application/octet-stream"); + const response = await fetch(signed.url, { + method: signed.method, + headers: signed.headers, + body: file, + }); + if (!response.ok) throw new Error(`Upload failed: ${await response.text()}`); + + // Requires CORS_ALLOWED_ORIGINS to include this page's exact origin. + return response.headers.get("ETag"); +} diff --git a/docs/integration-guide.md b/docs/integration-guide.md new file mode 100644 index 0000000..8ea42ae --- /dev/null +++ b/docs/integration-guide.md @@ -0,0 +1,98 @@ +# Frontend integration guide + +## Architecture + +Use a backend-for-frontend (BFF) as the trust boundary: + +```text +Browser → BFF (holds ACCESS_KEY + SECRET_KEY, issues presigned URLs) → I.R.I.S. Worker → Google Drive +``` + +Never put `SECRET_KEY` in browser code, a static site, or a public environment variable. The browser receives only a short-lived presigned URL for one method and object key. + +## Enable browser access + +Configure the Worker with an explicit allow-list of web origins: + +```ini +CORS_ALLOWED_ORIGINS=https://app.example.com,http://localhost:5173 +``` + +Set it to `*` only for a genuinely public, credential-free integration. Leaving it unset emits no CORS headers. I.R.I.S. exposes `ETag`, `Content-Range`, `Content-Length`, `Last-Modified`, `Accept-Ranges`, and `x-amz-request-id`, so browser code can inspect the headers that form the effective S3 response payload. + +### Server-to-server compatibility + +CORS does not restrict non-browser clients. `Access-Control-*` headers are emitted only when a request has an allowed `Origin`; a CLI, Dokploy, rclone, aws-cli, or other service request with no `Origin` keeps its normal S3 status and body. With an exact origin allow-list, responses also include `Vary: Origin`, including requests without an `Origin`, so shared caches cannot replay a no-origin object response to a browser. S3 clients ignore this cache-control metadata. + +An `OPTIONS` request with no `Origin`, or a preflight from a disallowed origin, receives the same bare `204` as a CORS-disabled Worker. A disallowed `Origin` never changes the underlying S3 result—it simply receives no `Access-Control-*` headers. + +## Presign in the BFF + +Use `@aws-sdk/s3-request-presigner` and send a limited expiry. I.R.I.S. validates `X-Amz-Expires` and rejects URLs after their signing time plus that value. + +```ts +import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3"; +import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; + +const client = new S3Client({ + endpoint: env.IRIS_ENDPOINT, + region: env.IRIS_REGION, + credentials: { + accessKeyId: env.IRIS_ACCESS_KEY, + secretAccessKey: env.IRIS_SECRET_KEY, + }, + forcePathStyle: true, +}); + +const url = await getSignedUrl( + client, + new PutObjectCommand({ + Bucket: "assets", + Key: "uploads/avatar.png", + ContentType: "image/png", + }), + { expiresIn: 300 }, +); +``` + +Keep the `ContentType` in the command and send precisely that same `Content-Type` from the browser. It becomes the Drive `mimeType`. A non-simple content type causes a preflight request, which is why the Worker CORS configuration is necessary. + +## Upload one object + +Use `Blob`, `File`, `ArrayBuffer`, or a typed array as the body. This lets the browser provide a known body length. Do **not** use a `ReadableStream` unless the BFF signs `x-amz-decoded-content-length` and the browser sends it: multipart part uploads require `Content-Length` or `x-amz-decoded-content-length`. + +```ts +const response = await fetch(presignedUrl, { + method: "PUT", + headers: { "Content-Type": file.type || "application/octet-stream" }, + body: file, +}); + +if (!response.ok) throw new Error(await response.text()); +const etag = response.headers.get("ETag"); +``` + +`ETag` is readable only when the request `Origin` is configured in `CORS_ALLOWED_ORIGINS`. + +## Multipart upload + +Cloudflare request limits mean a single PUT is best kept below roughly 100 MB. For larger files, have the BFF initiate the upload, issue a presigned URL for each part, and complete the upload after the browser returns all ETags. + +Use 16 MB parts as a practical baseline. Upload parts **strictly sequentially starting at 1**. I.R.I.S. waits up to 20 seconds for an earlier part; out-of-order work then receives `503 SlowDown`. Requests more than 64 part numbers ahead are rejected immediately. + +1. BFF signs `POST /bucket/key?uploads` and starts the multipart upload. +2. BFF issues a signed `PUT` URL for part 1; browser uploads it and records the `ETag`. +3. Repeat one part at a time for parts 2 through $n$. +4. BFF signs `POST /bucket/key?uploadId=…`; browser sends the `CompleteMultipartUpload` XML body containing the sequential part numbers and ETags. + +See [`examples/browser-multipart.ts`](./examples/browser-multipart.ts) for a complete browser-side loop. + +## Build a file browser + +List with `prefix` and `delimiter=/`: + +```text +GET /assets?prefix=photos/&delimiter=/ +``` + +Files appear in `Contents`; immediate child folders appear as `CommonPrefixes`. Google Drive directories are physical folders, not zero-byte marker objects. Read [limitations](./limitations.md) before designing pagination or rename/move features. diff --git a/docs/limitations.md b/docs/limitations.md new file mode 100644 index 0000000..d60ca7e --- /dev/null +++ b/docs/limitations.md @@ -0,0 +1,35 @@ +# Limitations + +I.R.I.S. is a narrow S3 compatibility layer. Design frontend features around the behavior below. + +## Listing is not pageable + +`max-keys`, `continuation-token`, and `marker` are ignored. I.R.I.S. scans at most 5000 Google Drive nodes and then sets `IsTruncated=true`; it does not provide a continuation token, so additional results cannot be requested. The XML value `MaxKeys=1000` is a fixed compatibility value, not the real scan limit. + +Use narrow prefixes and `delimiter=/` for a file browser. Do not build an infinite-scrolling full-drive browser. + +## No server-side rename or move + +`CopyObject` and `UploadPartCopy` return `501 NotImplemented`. There is no server-side rename or move because both require copying. Implement rename/move as download, re-upload under the target key, then delete the original—subject to bandwidth, time, and error handling. + +## Unsupported S3 features + +- `ListBuckets`, `CreateBucket`, and `DeleteBucket` +- batch `DeleteObjects` +- object versioning, ACLs, object tags, lifecycle rules, and `x-amz-meta-*` user metadata +- CopyObject and UploadPartCopy +- discovering active multipart uploads: `ListMultipartUploads` always reports an empty list + +Bucket `?acl`, `?versioning`, and `?location` requests are currently treated as regular bucket listing requests rather than errors. Do not rely on this accidental compatibility. + +## Directories and objects + +Directories are physical Google Drive folders created from object-key path segments. I.R.I.S. does not create zero-byte directory marker objects. An empty prefix can therefore exist as a Drive folder without an S3 marker object. + +## Multipart behavior + +Parts must be uploaded strictly in consecutive order beginning at part 1. Parts are immutable after they have committed. An incomplete upload expires after 24 hours. `ETAG_STYLE=md5` returns Google Drive's file MD5 for completed multipart objects; `ETAG_STYLE=multipart` is available only for clients that require an S3-style composite ETag. + +## Quotas and plan limits + +Google Drive's free tier has 15 GB total storage and a 750 GB/day upload limit. Cloudflare applies request-size limits before a request reaches the Worker; use multipart uploads for large browser uploads. diff --git a/docs/openapi.yaml b/docs/openapi.yaml new file mode 100644 index 0000000..0722319 --- /dev/null +++ b/docs/openapi.yaml @@ -0,0 +1,391 @@ +openapi: 3.1.0 +info: + title: I.R.I.S. Google Drive S3 API + version: 1.0.0 + description: | + An intentionally small S3-compatible object-storage API backed by Google Drive. + Authenticate every non-public request with AWS Signature Version 4. Browser clients + should receive short-lived presigned URLs from a backend-for-frontend, never credentials. +servers: + - url: https://{worker-host} + variables: + worker-host: + default: your-worker.example.workers.dev +security: + - sigv4: [] +tags: + - name: Objects + - name: Buckets + - name: Multipart uploads +paths: + /{bucket}: + parameters: + - $ref: '#/components/parameters/Bucket' + get: + tags: [Buckets] + operationId: listObjects + summary: List objects in a bucket + description: List objects recursively, or use `delimiter=/` to return immediate folders as `CommonPrefixes`. + parameters: + - $ref: '#/components/parameters/Prefix' + - $ref: '#/components/parameters/Delimiter' + responses: + '200': + description: Object listing. + content: + application/xml: + schema: { $ref: '#/components/schemas/ListBucketResult' } + '403': { $ref: '#/components/responses/AccessDenied' } + '405': { $ref: '#/components/responses/MethodNotAllowed' } + head: + tags: [Buckets] + operationId: headBucket + summary: Check bucket access + responses: + '200': { description: The bucket is configured and accessible. } + '403': { $ref: '#/components/responses/AccessDenied' } + '405': { $ref: '#/components/responses/MethodNotAllowed' } + /{bucket}?uploads: + parameters: + - $ref: '#/components/parameters/Bucket' + get: + tags: [Multipart uploads] + operationId: listMultipartUploads + summary: List multipart uploads + description: Always returns an empty list; active multipart uploads cannot be enumerated. + responses: + '200': + description: Empty multipart upload listing. + content: + application/xml: + schema: { $ref: '#/components/schemas/ListMultipartUploadsResult' } + '403': { $ref: '#/components/responses/AccessDenied' } + /{bucket}/{key}: + parameters: + - $ref: '#/components/parameters/Bucket' + - $ref: '#/components/parameters/Key' + put: + tags: [Objects] + operationId: putObject + summary: Create or replace an object + parameters: + - $ref: '#/components/parameters/ContentType' + requestBody: + required: true + content: + application/octet-stream: + schema: { type: string, format: binary } + '*/*': + schema: { type: string, format: binary } + responses: + '200': + description: Object uploaded. + headers: + ETag: { $ref: '#/components/headers/ETag' } + '403': { $ref: '#/components/responses/AccessDenied' } + '501': { $ref: '#/components/responses/NotImplemented' } + get: + tags: [Objects] + operationId: getObject + summary: Download an object + parameters: + - name: Range + in: header + schema: { type: string, example: bytes=0-1023 } + responses: + '200': + description: Full object data. + headers: + ETag: { $ref: '#/components/headers/ETag' } + Content-Length: { $ref: '#/components/headers/ContentLength' } + Last-Modified: { $ref: '#/components/headers/LastModified' } + Accept-Ranges: { schema: { type: string, example: bytes } } + content: + application/octet-stream: + schema: { type: string, format: binary } + '206': + description: Requested byte range. + headers: + Content-Range: { schema: { type: string, example: bytes 0-1023/4096 } } + Content-Length: { $ref: '#/components/headers/ContentLength' } + content: + application/octet-stream: + schema: { type: string, format: binary } + '403': { $ref: '#/components/responses/AccessDenied' } + '404': { $ref: '#/components/responses/NoSuchKey' } + head: + tags: [Objects] + operationId: headObject + summary: Get object metadata + responses: + '200': + description: Object metadata in headers. + headers: + ETag: { $ref: '#/components/headers/ETag' } + Content-Length: { $ref: '#/components/headers/ContentLength' } + Last-Modified: { $ref: '#/components/headers/LastModified' } + Accept-Ranges: { schema: { type: string, example: bytes } } + '403': { $ref: '#/components/responses/AccessDenied' } + '404': { $ref: '#/components/responses/NoSuchKey' } + delete: + tags: [Objects] + operationId: deleteObject + summary: Delete an object + responses: + '204': { description: Object deleted. } + '403': { $ref: '#/components/responses/AccessDenied' } + '404': { $ref: '#/components/responses/NoSuchKey' } + /{bucket}/{key}?uploads: + parameters: + - $ref: '#/components/parameters/Bucket' + - $ref: '#/components/parameters/Key' + post: + tags: [Multipart uploads] + operationId: createMultipartUpload + summary: Initiate a multipart upload + parameters: + - $ref: '#/components/parameters/ContentType' + responses: + '200': + description: Upload initiated. + content: + application/xml: + schema: { $ref: '#/components/schemas/InitiateMultipartUploadResult' } + '403': { $ref: '#/components/responses/AccessDenied' } + '501': { $ref: '#/components/responses/NotImplemented' } + /{bucket}/{key}?uploadId={uploadId}&partNumber={partNumber}: + parameters: + - $ref: '#/components/parameters/Bucket' + - $ref: '#/components/parameters/Key' + - $ref: '#/components/parameters/UploadId' + - $ref: '#/components/parameters/PartNumber' + put: + tags: [Multipart uploads] + operationId: uploadPart + summary: Upload one multipart part + description: Parts must arrive strictly sequentially beginning with part number 1. + requestBody: + required: true + content: + application/octet-stream: + schema: { type: string, format: binary } + responses: + '200': + description: Part accepted. + headers: + ETag: { $ref: '#/components/headers/ETag' } + '400': { $ref: '#/components/responses/InvalidArgument' } + '403': { $ref: '#/components/responses/AccessDenied' } + '404': { $ref: '#/components/responses/NoSuchUpload' } + '503': { $ref: '#/components/responses/SlowDown' } + /{bucket}/{key}?uploadId={uploadId}: + parameters: + - $ref: '#/components/parameters/Bucket' + - $ref: '#/components/parameters/Key' + - $ref: '#/components/parameters/UploadId' + post: + tags: [Multipart uploads] + operationId: completeMultipartUpload + summary: Complete a multipart upload + requestBody: + required: true + content: + application/xml: + schema: { $ref: '#/components/schemas/CompleteMultipartUpload' } + responses: + '200': + description: Upload completed. + content: + application/xml: + schema: { $ref: '#/components/schemas/CompleteMultipartUploadResult' } + '400': { $ref: '#/components/responses/InvalidArgument' } + '403': { $ref: '#/components/responses/AccessDenied' } + '404': { $ref: '#/components/responses/NoSuchUpload' } + get: + tags: [Multipart uploads] + operationId: listParts + summary: List uploaded parts + parameters: + - name: part-number-marker + in: query + schema: { type: integer, minimum: 0, default: 0 } + - name: max-parts + in: query + schema: { type: integer, minimum: 1, maximum: 1000, default: 1000 } + responses: + '200': + description: Current part list. + content: + application/xml: + schema: { $ref: '#/components/schemas/ListPartsResult' } + '400': { $ref: '#/components/responses/InvalidArgument' } + '403': { $ref: '#/components/responses/AccessDenied' } + '404': { $ref: '#/components/responses/NoSuchUpload' } + delete: + tags: [Multipart uploads] + operationId: abortMultipartUpload + summary: Abort a multipart upload + responses: + '204': { description: Upload aborted. } + '403': { $ref: '#/components/responses/AccessDenied' } + '404': { $ref: '#/components/responses/NoSuchUpload' } +components: + securitySchemes: + sigv4: + type: apiKey + in: header + name: Authorization + description: AWS Signature Version 4 header authentication or equivalent `X-Amz-*` presigned query parameters. + parameters: + Bucket: + name: bucket + in: path + required: true + schema: { type: string } + Key: + name: key + in: path + required: true + schema: { type: string } + Prefix: + name: prefix + in: query + schema: { type: string, default: '' } + Delimiter: + name: delimiter + in: query + schema: { type: string, example: / } + UploadId: + name: uploadId + in: query + required: true + schema: { type: string } + PartNumber: + name: partNumber + in: query + required: true + schema: { type: integer, minimum: 1, maximum: 10000 } + ContentType: + name: Content-Type + in: header + schema: { type: string, example: application/octet-stream } + headers: + ETag: + schema: { type: string, example: '"d41d8cd98f00b204e9800998ecf8427e"' } + ContentLength: + schema: { type: integer, minimum: 0 } + LastModified: + schema: { type: string, format: date-time } + responses: + AccessDenied: + description: Authentication failed, the access key is not recognized, the presigned URL expired, or bucket access is denied. + content: { application/xml: { schema: { $ref: '#/components/schemas/S3Error' } } } + NoSuchKey: + description: The object does not exist. + content: { application/xml: { schema: { $ref: '#/components/schemas/S3Error' } } } + NoSuchUpload: + description: The multipart upload does not exist. + content: { application/xml: { schema: { $ref: '#/components/schemas/S3Error' } } } + InvalidArgument: + description: A required parameter or body is invalid. + content: { application/xml: { schema: { $ref: '#/components/schemas/S3Error' } } } + NotImplemented: + description: This S3 operation is not supported. + content: { application/xml: { schema: { $ref: '#/components/schemas/S3Error' } } } + SlowDown: + description: Parts are out of sequence or the upload is busy; retry later. + content: { application/xml: { schema: { $ref: '#/components/schemas/S3Error' } } } + MethodNotAllowed: + description: The method/path combination is unsupported. + content: { application/xml: { schema: { $ref: '#/components/schemas/S3Error' } } } + schemas: + S3Error: + type: object + xml: { name: Error } + required: [Code, Message, RequestId] + properties: + Code: { type: string, xml: { name: Code } } + Message: { type: string, xml: { name: Message } } + Resource: { type: string, xml: { name: Resource } } + RequestId: { type: string, xml: { name: RequestId } } + Object: + type: object + xml: { name: Contents } + properties: + Key: { type: string } + LastModified: { type: string, format: date-time } + ETag: { type: string } + Size: { type: integer } + StorageClass: { type: string, example: STANDARD } + ListBucketResult: + type: object + xml: { name: ListBucketResult, namespace: 'http://s3.amazonaws.com/doc/2006-03-01/' } + properties: + Name: { type: string } + Prefix: { type: string } + Delimiter: { type: string } + MaxKeys: { type: integer, example: 1000 } + IsTruncated: { type: boolean } + Contents: { type: array, items: { $ref: '#/components/schemas/Object' }, xml: { wrapped: false } } + InitiateMultipartUploadResult: + type: object + xml: { name: InitiateMultipartUploadResult, namespace: 'http://s3.amazonaws.com/doc/2006-03-01/' } + required: [Bucket, Key, UploadId] + properties: + Bucket: { type: string } + Key: { type: string } + UploadId: { type: string } + CompleteMultipartUpload: + type: object + xml: { name: CompleteMultipartUpload } + required: [Part] + properties: + Part: + type: array + minItems: 1 + xml: { wrapped: false } + items: + type: object + xml: { name: Part } + required: [PartNumber, ETag] + properties: + PartNumber: { type: integer } + ETag: { type: string } + CompleteMultipartUploadResult: + type: object + xml: { name: CompleteMultipartUploadResult, namespace: 'http://s3.amazonaws.com/doc/2006-03-01/' } + properties: + Location: { type: string } + Bucket: { type: string } + Key: { type: string } + ETag: { type: string } + ListPartsResult: + type: object + xml: { name: ListPartsResult, namespace: 'http://s3.amazonaws.com/doc/2006-03-01/' } + properties: + Bucket: { type: string } + Key: { type: string } + UploadId: { type: string } + NextPartNumberMarker: { type: integer } + IsTruncated: { type: boolean } + Part: + type: array + xml: { wrapped: false } + items: + type: object + properties: + PartNumber: { type: integer } + LastModified: { type: string, format: date-time } + ETag: { type: string } + Size: { type: integer } + ListMultipartUploadsResult: + type: object + xml: { name: ListMultipartUploadsResult, namespace: 'http://s3.amazonaws.com/doc/2006-03-01/' } + properties: + Bucket: { type: string } + KeyMarker: { type: string } + UploadIdMarker: { type: string } + NextKeyMarker: { type: string } + NextUploadIdMarker: { type: string } + MaxUploads: { type: integer, example: 1000 } + IsTruncated: { type: boolean, example: false } diff --git a/scripts/get-google-refresh-token.mjs b/scripts/get-google-refresh-token.mjs index 7e531d3..af0161e 100644 --- a/scripts/get-google-refresh-token.mjs +++ b/scripts/get-google-refresh-token.mjs @@ -20,9 +20,9 @@ // Optional: --port 8976 (must not be in use; Desktop-app clients accept any // loopback port automatically, no need to register it in Google Cloud Console). +import { exec } from "node:child_process"; import { randomBytes } from "node:crypto"; import http from "node:http"; -import { exec } from "node:child_process"; import { URL } from "node:url"; const SCOPE = "https://www.googleapis.com/auth/drive"; @@ -137,10 +137,7 @@ async function main() { process.exit(1); } if (!tokenData.refresh_token) { - console.error( - "No refresh_token was returned. This usually means the account already has an active grant.\n" + - "Revoke it at https://myaccount.google.com/permissions and re-run this script.", - ); + console.error("No refresh_token was returned. This usually means the account already has an active grant.\n" + "Revoke it at https://myaccount.google.com/permissions and re-run this script."); process.exit(1); } diff --git a/src/aws-signature.ts b/src/aws-signature.ts index 30b094b..46eaedb 100644 --- a/src/aws-signature.ts +++ b/src/aws-signature.ts @@ -1,5 +1,11 @@ +import type { S3ErrorCode } from "./s3-errors"; import type { Env } from "./types"; +const MAX_PRESIGN_EXPIRY_SECONDS = 7 * 24 * 60 * 60; +const MAX_CLOCK_SKEW_MS = 15 * 60 * 1000; + +export type VerifyResult = { ok: true } | { ok: false; code: S3ErrorCode; message?: string }; + function encodeRFC3986(str: string): string { return encodeURIComponent(str).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`); } @@ -84,8 +90,35 @@ async function createCanonicalRequest(request: Request, isQueryAuth: boolean): P return [method, canonicalUri, params, canonicalHeaders, signedHeaders, payloadHash].join("\n"); } +function parseAmzDate(datetime: string): number | null { + const match = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z$/.exec(datetime); + if (!match) return null; + + const [, year, month, day, hour, minute, second] = match; + const timestamp = Date.UTC(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), Number(second)); + const parsed = new Date(timestamp); + if (parsed.getUTCFullYear() !== Number(year) || parsed.getUTCMonth() !== Number(month) - 1 || parsed.getUTCDate() !== Number(day) || parsed.getUTCHours() !== Number(hour) || parsed.getUTCMinutes() !== Number(minute) || parsed.getUTCSeconds() !== Number(second)) { + return null; + } + + return timestamp; +} + +function constantTimeEqual(left: string, right: string): boolean { + if (left.length !== right.length) return false; + let mismatch = 0; + for (let index = 0; index < left.length; index++) { + mismatch |= left.charCodeAt(index) ^ right.charCodeAt(index); + } + return mismatch === 0; +} + +function signatureMismatch(): VerifyResult { + return { ok: false, code: "SignatureDoesNotMatch" }; +} + /** Verifies an AWS Signature V4 signature carried in either the Authorization header or presigned query params. */ -export async function verifySignature(request: Request, env: Env): Promise { +export async function verifySignature(request: Request, env: Env): Promise { const url = new URL(request.url); const headers = request.headers; @@ -99,15 +132,32 @@ export async function verifySignature(request: Request, env: Env): Promise MAX_PRESIGN_EXPIRY_SECONDS || Date.now() > signedAt + expiresIn * 1000) { + return { ok: false, code: "AccessDenied", message: "Request has expired" }; + } + } else if (Math.abs(Date.now() - signedAt) > MAX_CLOCK_SKEW_MS) { + return { ok: false, code: "RequestTimeTooSkewed" }; + } const date = datetime.substring(0, 8); + const credential = isQueryAuth ? (url.searchParams.get("X-Amz-Credential") ?? "") : (/Credential=([^,\s]+)/.exec(headers.get("Authorization") ?? "")?.[1] ?? ""); + const credentialParts = credential.split("/"); + if (credentialParts.length !== 5 || credentialParts[0] !== env.ACCESS_KEY || credentialParts.slice(1).join("/") !== `${date}/${env.REGION}/s3/aws4_request`) { + return { ok: false, code: "AccessDenied" }; + } const canonicalRequest = await createCanonicalRequest(request, isQueryAuth); const hashedCanonicalRequest = await sha256(canonicalRequest); @@ -124,9 +174,9 @@ export async function verifySignature(request: Request, env: Env): Promise origin.trim()) + .filter(Boolean); +} + +function hasWildcardOrigin(env: Env): boolean { + return env.CORS_ALLOWED_ORIGINS?.trim() === "*"; +} + +function appendVary(headers: Headers, value: string): void { + const existing = headers.get("Vary"); + if (!existing) { + headers.set("Vary", value); + return; + } + + if (!existing.split(",").some((part) => part.trim().toLowerCase() === value.toLowerCase())) { + headers.set("Vary", `${existing}, ${value}`); + } +} + +/** Returns the permitted value for Access-Control-Allow-Origin, or null when CORS is disabled or denied. */ +export function resolveOrigin(request: Request, env: Env): string | null { + const origin = request.headers.get("Origin"); + if (!origin) return null; + if (hasWildcardOrigin(env)) return "*"; + return configuredOrigins(env).includes(origin) ? origin : null; +} + +/** Builds an unauthenticated browser preflight response. */ +export function preflightResponse(request: Request, env: Env): Response { + const headers = new Headers(); + const origin = resolveOrigin(request, env); + + if (origin) { + headers.set("Access-Control-Allow-Origin", origin); + headers.set("Access-Control-Allow-Methods", ALLOWED_METHODS); + headers.set("Access-Control-Allow-Headers", request.headers.get("Access-Control-Request-Headers") ?? DEFAULT_ALLOWED_HEADERS); + headers.set("Access-Control-Max-Age", "86400"); + if (!hasWildcardOrigin(env)) appendVary(headers, "Origin"); + } + + return new Response(null, { status: 204, headers }); +} + +/** Adds CORS response headers without mutating the response supplied by the request handler. */ +export function withCors(response: Response, request: Request, env: Env): Response { + const headers = new Headers(response.headers); + + if (env.CORS_ALLOWED_ORIGINS?.trim() && !hasWildcardOrigin(env)) { + appendVary(headers, "Origin"); + } + + const origin = resolveOrigin(request, env); + if (origin) { + headers.set("Access-Control-Allow-Origin", origin); + headers.set("Access-Control-Expose-Headers", EXPOSED_HEADERS); + } + + return new Response(response.body, { status: response.status, statusText: response.statusText, headers }); +} diff --git a/src/docs.ts b/src/docs.ts new file mode 100644 index 0000000..de29098 --- /dev/null +++ b/src/docs.ts @@ -0,0 +1,36 @@ +import openApiSpec from "../docs/openapi.yaml"; + +export const OPENAPI_PATH = "/openapi.yaml"; +export const DOCS_PATH = "/docs"; + +export function openApiResponse(): Response { + return new Response(openApiSpec, { + headers: { + "Content-Type": "application/yaml; charset=utf-8", + "Cache-Control": "public, max-age=300", + }, + }); +} + +export function docsResponse(): Response { + const html = ` + + + + + + I.R.I.S. S3 API reference + + + + + +`; + + return new Response(html, { + headers: { + "Content-Type": "text/html; charset=utf-8", + "Cache-Control": "public, max-age=300", + }, + }); +} diff --git a/src/google-drive.ts b/src/google-drive.ts index 1cbaf93..33c07f6 100644 --- a/src/google-drive.ts +++ b/src/google-drive.ts @@ -220,9 +220,10 @@ export async function streamDownloadFromDrive(accessToken: string, bucket: strin console.error(await downloadRes.text()); throw new Error("Download failed"); } + if (!downloadRes.body) throw new Error("Download response had no body"); return { - body: downloadRes.body!, + body: downloadRes.body, contentType: file.mimeType || "application/octet-stream", size: parseInt(file.size || "0", 10), id: file.id, diff --git a/src/index.ts b/src/index.ts index d7b81b7..2e8220e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,7 @@ import { verifySignature } from "./aws-signature"; import { isAllowedBucket, isPublicReadBucket } from "./bucket-access"; +import { preflightResponse, withCors } from "./cors"; +import * as docs from "./docs"; import { getAccessToken } from "./google-drive"; import { MultipartUploadDO } from "./multipart-do"; import { dispatch } from "./router"; @@ -10,27 +12,33 @@ export { MultipartUploadDO }; export default { async fetch(request: Request, env: Env, _ctx: ExecutionContext): Promise { - if (request.method === "OPTIONS") return new Response(null, { status: 204 }); + if (request.method === "OPTIONS") return preflightResponse(request, env); const url = new URL(request.url); + if (env.ENABLE_DOCS !== "false") { + if (request.method === "GET" && url.pathname === docs.OPENAPI_PATH) return withCors(docs.openApiResponse(), request, env); + if (request.method === "GET" && url.pathname === docs.DOCS_PATH && !isAllowedBucket("docs", env)) return withCors(docs.docsResponse(), request, env); + } + const pathParts = url.pathname.split("/").filter(Boolean); const bucket = pathParts[0] || ""; const objectKey = pathParts.slice(1).join("/"); const resource = url.pathname || "/"; try { - if (!isAllowedBucket(bucket, env)) return s3Error("AccessDenied", 403, undefined, resource, request.method === "HEAD"); + if (!isAllowedBucket(bucket, env)) return withCors(s3Error("AccessDenied", 403, undefined, resource, request.method === "HEAD"), request, env); const isPublicRead = isPublicReadBucket(bucket, env) && (request.method === "GET" || request.method === "HEAD"); - if (!isPublicRead && !(await verifySignature(request, env))) { - return s3Error("SignatureDoesNotMatch", 403, undefined, resource, request.method === "HEAD"); + const signature = isPublicRead ? { ok: true as const } : await verifySignature(request, env); + if (!signature.ok) { + return withCors(s3Error(signature.code, 403, signature.message, resource, request.method === "HEAD"), request, env); } - return await dispatch(request, env, await getAccessToken(env), bucket, objectKey); + return withCors(await dispatch(request, env, await getAccessToken(env), bucket, objectKey), request, env); } catch (error) { - if (error instanceof S3Exception) return s3Error(error.code, error.status, error.message, resource, request.method === "HEAD", error.headers); + if (error instanceof S3Exception) return withCors(s3Error(error.code, error.status, error.message, resource, request.method === "HEAD", error.headers), request, env); console.error(JSON.stringify({ message: "request failed", error: error instanceof Error ? error.message : String(error), method: request.method, path: url.pathname })); - return s3Error("InternalError", 500, undefined, resource, request.method === "HEAD"); + return withCors(s3Error("InternalError", 500, undefined, resource, request.method === "HEAD"), request, env); } }, } satisfies ExportedHandler; diff --git a/src/multipart-do.ts b/src/multipart-do.ts index 8d0fc4c..d8299ca 100644 --- a/src/multipart-do.ts +++ b/src/multipart-do.ts @@ -57,6 +57,12 @@ export class MultipartUploadDO extends DurableObject { return this.ctx.storage.sql.exec("SELECT v FROM state WHERE k = ?", key).toArray()[0]?.v as T | undefined; } + private requiredValue(key: string): T { + const value = this.getValue(key); + if (value === undefined) throw new Error(`Missing multipart upload state: ${key}`); + return value; + } + private setValue(key: string, value: ArrayBuffer | string | number | null): void { this.ctx.storage.sql.exec("INSERT INTO state (k, v) VALUES (?, ?) ON CONFLICT(k) DO UPDATE SET v = excluded.v", key, value); } @@ -97,7 +103,7 @@ export class MultipartUploadDO extends DurableObject { private async resyncExpiredLease(inFlight: InFlight, requestId: string, partLen: number): Promise { if (partLen !== inFlight.partLen) return { kind: "error", code: "InvalidPart", message: "A retried part must have the same decoded length" }; const accessToken = await getAccessToken(this.env); - const actualOffset = await queryStatus(this.getValue("uploadUrl")!, accessToken); + const actualOffset = await queryStatus(this.requiredValue("uploadUrl"), accessToken); const partStartFileOffset = inFlight.driveOffsetAtStart + inFlight.carryLen; let skipBytes = 0; if (actualOffset === inFlight.driveOffsetAtStart) { @@ -129,7 +135,7 @@ export class MultipartUploadDO extends DurableObject { leaseExpiresAt: Date.now() + LEASE_MS, }; this.setValue("inFlight", JSON.stringify(inFlight)); - return { kind: "admit", uploadUrl: this.getValue("uploadUrl")!, driveOffset, carry, sendLen, skipBytes }; + return { kind: "admit", uploadUrl: this.requiredValue("uploadUrl"), driveOffset, carry, sendLen, skipBytes }; } async beginPart(requestId: string, partNumber: number, partLen: number): Promise { @@ -229,15 +235,15 @@ export class MultipartUploadDO extends DurableObject { const accessToken = await getAccessToken(this.env); let metadata: DriveUploadResult; if (total === 0) { - await cancelSession(this.getValue("uploadUrl")!, accessToken); + await cancelSession(this.requiredValue("uploadUrl"), accessToken); metadata = await createEmptyFile(accessToken, { - name: this.getValue("fileName")!, - parents: [this.getValue("parentFolderId")!], - mimeType: this.getValue("mimeType")!, + name: this.requiredValue("fileName"), + parents: [this.requiredValue("parentFolderId")], + mimeType: this.requiredValue("mimeType"), existingFileId: this.getValue("existingFileId"), }); } else { - metadata = await putFinalChunk(this.getValue("uploadUrl")!, accessToken, driveOffset, total, carry); + metadata = await putFinalChunk(this.requiredValue("uploadUrl"), accessToken, driveOffset, total, carry); } const partEtags = stored.map((part) => part.etag); await this.ctx.storage.deleteAlarm(); @@ -259,7 +265,7 @@ export class MultipartUploadDO extends DurableObject { async abort(): Promise { if (!this.hasUpload()) return false; const accessToken = await getAccessToken(this.env); - await cancelSession(this.getValue("uploadUrl")!, accessToken); + await cancelSession(this.requiredValue("uploadUrl"), accessToken); await this.ctx.storage.deleteAlarm(); await this.ctx.storage.deleteAll(); for (const waiter of this.waiters.values()) { diff --git a/src/s3-errors.ts b/src/s3-errors.ts index 549eda4..59d9bcd 100644 --- a/src/s3-errors.ts +++ b/src/s3-errors.ts @@ -1,10 +1,11 @@ import { escapeXml } from "./s3-xml"; -export type S3ErrorCode = "AccessDenied" | "SignatureDoesNotMatch" | "NoSuchKey" | "NoSuchUpload" | "InvalidPart" | "InvalidPartOrder" | "EntityTooLarge" | "MalformedXML" | "InvalidArgument" | "MethodNotAllowed" | "NotImplemented" | "SlowDown" | "InternalError"; +export type S3ErrorCode = "AccessDenied" | "SignatureDoesNotMatch" | "RequestTimeTooSkewed" | "NoSuchKey" | "NoSuchUpload" | "InvalidPart" | "InvalidPartOrder" | "EntityTooLarge" | "MalformedXML" | "InvalidArgument" | "MethodNotAllowed" | "NotImplemented" | "SlowDown" | "InternalError"; const DEFAULT_MESSAGES: Record = { AccessDenied: "Access Denied", SignatureDoesNotMatch: "The request signature we calculated does not match the signature you provided.", + RequestTimeTooSkewed: "The difference between the request time and the server's time is too large.", NoSuchKey: "The specified key does not exist.", NoSuchUpload: "The specified multipart upload does not exist.", InvalidPart: "One or more of the specified parts could not be found.", diff --git a/src/text-modules.d.ts b/src/text-modules.d.ts new file mode 100644 index 0000000..3897aa2 --- /dev/null +++ b/src/text-modules.d.ts @@ -0,0 +1,4 @@ +declare module "*.yaml" { + const content: string; + export default content; +} diff --git a/src/types.ts b/src/types.ts index 44772fc..a30ec2b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -12,6 +12,8 @@ export interface Env { PUBLIC_READ_BUCKETS?: string; ALLOW_MULTIPART?: string; ETAG_STYLE?: "md5" | "multipart"; + CORS_ALLOWED_ORIGINS?: string; + ENABLE_DOCS?: string; } export interface GoogleDriveFile { diff --git a/test/cors.test.ts b/test/cors.test.ts new file mode 100644 index 0000000..aee85c7 --- /dev/null +++ b/test/cors.test.ts @@ -0,0 +1,119 @@ +import { AwsClient } from "aws4fetch"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { preflightResponse, withCors } from "../src/cors"; +import worker from "../src/index"; +import type { Env } from "../src/types"; + +import { env } from "cloudflare:test"; + +const ENV = env as unknown as Env; +const ENDPOINT = "https://s3-api.example.com"; +const ORIGIN = "http://localhost:5173"; +const CTX = { waitUntil: vi.fn(), passThroughOnException: vi.fn() } as unknown as ExecutionContext; + +async function signed(path: string, init: RequestInit): Promise { + const aws = new AwsClient({ accessKeyId: ENV.ACCESS_KEY, secretAccessKey: ENV.SECRET_KEY, region: ENV.REGION, service: "s3" }); + const bodyLength = typeof init.body === "string" ? new TextEncoder().encode(init.body).byteLength : init.body instanceof Uint8Array ? init.body.byteLength : undefined; + return aws.sign(`${ENDPOINT}${path}`, { + ...init, + headers: { "x-amz-content-sha256": "UNSIGNED-PAYLOAD", ...(bodyLength === undefined ? {} : { "x-amz-decoded-content-length": String(bodyLength) }), ...init.headers }, + }); +} + +function fakeGoogleFetch(input: string | URL | Request, init?: RequestInit): Promise { + const request = input instanceof Request ? input : new Request(input, init); + const url = new URL(request.url); + if (url.hostname === "oauth2.googleapis.com") return Promise.resolve(Response.json({ access_token: "token", expires_in: 3600 })); + if (url.pathname === "/drive/v3/files" && request.method === "GET") return Promise.resolve(Response.json({ files: [] })); + if (url.pathname === "/drive/v3/files" && request.method === "POST") return Promise.resolve(Response.json({ id: "folder-1" })); + if (url.pathname.startsWith("/upload/drive/v3/files")) return Promise.resolve(new Response(null, { headers: { Location: "https://www.googleapis.com/upload/session/test" } })); + if (url.pathname === "/upload/session/test") return Promise.resolve(Response.json({ id: "file-1", name: "file.txt", md5Checksum: "d41d8cd98f00b204e9800998ecf8427e" })); + return Promise.resolve(new Response("Not Found", { status: 404 })); +} + +afterEach(() => vi.unstubAllGlobals()); + +describe("CORS", () => { + it("returns allow-list preflight headers", async () => { + const response = preflightResponse( + new Request(`${ENDPOINT}/test-bucket/file.txt`, { + method: "OPTIONS", + headers: { Origin: ORIGIN, "Access-Control-Request-Method": "PUT", "Access-Control-Request-Headers": "content-type,x-amz-date" }, + }), + ENV, + ); + + expect(response.status).toBe(204); + expect(response.headers.get("Access-Control-Allow-Origin")).toBe(ORIGIN); + expect(response.headers.get("Access-Control-Allow-Methods")).toBe("GET, HEAD, PUT, POST, DELETE, OPTIONS"); + expect(response.headers.get("Access-Control-Allow-Headers")).toBe("content-type,x-amz-date"); + expect(response.headers.get("Access-Control-Max-Age")).toBe("86400"); + expect(response.headers.get("Vary")).toBe("Origin"); + }); + + it("returns a bare 204 for a disallowed origin", async () => { + const response = preflightResponse(new Request(`${ENDPOINT}/test-bucket/file.txt`, { method: "OPTIONS", headers: { Origin: "https://not-allowed.example", "Access-Control-Request-Method": "PUT" } }), ENV); + + expect(response.status).toBe(204); + expect([...response.headers]).toEqual([]); + }); + + it("keeps OPTIONS requests without an Origin byte-compatible with the prior bare 204", () => { + const response = preflightResponse(new Request(`${ENDPOINT}/test-bucket/file.txt`, { method: "OPTIONS" }), ENV); + + expect(response.status).toBe(204); + expect([...response.headers]).toEqual([]); + }); + + it("exposes ETag for successful PUT responses", async () => { + vi.stubGlobal("fetch", vi.fn(fakeGoogleFetch)); + const request = await signed("/test-bucket/file.txt", { method: "PUT", body: "hello", headers: { Origin: ORIGIN, "Content-Type": "text/plain" } }); + const response = await worker.fetch(request, ENV, CTX); + + expect(response.status).toBe(200); + expect(response.headers.get("ETag")).toBe('"d41d8cd98f00b204e9800998ecf8427e"'); + expect(response.headers.get("Access-Control-Allow-Origin")).toBe(ORIGIN); + expect(response.headers.get("Access-Control-Expose-Headers")).toContain("ETag"); + }); + + it("adds CORS headers to access-denied and missing-key errors", async () => { + const denied = await worker.fetch(new Request(`${ENDPOINT}/not-a-bucket`, { headers: { Origin: ORIGIN } }), ENV, CTX); + expect(denied.status).toBe(403); + expect(denied.headers.get("Access-Control-Allow-Origin")).toBe(ORIGIN); + + vi.stubGlobal("fetch", vi.fn(fakeGoogleFetch)); + const missing = await worker.fetch(await signed("/test-bucket/missing.txt", { method: "GET", headers: { Origin: ORIGIN } }), ENV, CTX); + expect(missing.status).toBe(404); + expect(await missing.text()).toContain("NoSuchKey"); + expect(missing.headers.get("Access-Control-Allow-Origin")).toBe(ORIGIN); + }); + + it("does not alter the S3 response for a disallowed Origin", async () => { + const response = await worker.fetch(new Request(`${ENDPOINT}/not-a-bucket`, { headers: { Origin: "https://not-allowed.example" } }), ENV, CTX); + + expect(response.status).toBe(403); + expect(await response.text()).toContain("AccessDenied"); + expect(response.headers.get("Access-Control-Allow-Origin")).toBeNull(); + expect(response.headers.get("Access-Control-Expose-Headers")).toBeNull(); + expect(response.headers.get("Vary")).toBe("Origin"); + }); + + it("does not attach Access-Control headers to server-to-server responses without Origin", () => { + const response = withCors(new Response("S3 response", { status: 200, headers: { ETag: '"etag"' } }), new Request(`${ENDPOINT}/test-bucket/file.txt`), ENV); + + expect(response.status).toBe(200); + expect(response.headers.get("ETag")).toBe('"etag"'); + expect(response.headers.get("Access-Control-Allow-Origin")).toBeNull(); + expect(response.headers.get("Access-Control-Expose-Headers")).toBeNull(); + expect(response.headers.get("Vary")).toBe("Origin"); + }); + + it("emits no CORS headers when CORS_ALLOWED_ORIGINS is unset", () => { + const request = new Request(`${ENDPOINT}/test-bucket/file.txt`, { headers: { Origin: ORIGIN } }); + const response = withCors(new Response(null, { headers: { ETag: '"etag"' } }), request, { ...ENV, CORS_ALLOWED_ORIGINS: undefined }); + expect(response.headers.get("Access-Control-Allow-Origin")).toBeNull(); + expect(response.headers.get("Access-Control-Expose-Headers")).toBeNull(); + expect(response.headers.get("Vary")).toBeNull(); + }); +}); diff --git a/test/docs.test.ts b/test/docs.test.ts new file mode 100644 index 0000000..d6a20e7 --- /dev/null +++ b/test/docs.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it, vi } from "vitest"; + +import worker from "../src/index"; +import type { Env } from "../src/types"; + +import { env } from "cloudflare:test"; + +const ENV = env as unknown as Env; +const ENDPOINT = "https://s3-api.example.com"; +const CTX = { waitUntil: vi.fn(), passThroughOnException: vi.fn() } as unknown as ExecutionContext; + +describe("API documentation routes", () => { + it("serves the Scalar shell and raw OpenAPI document", async () => { + const docs = await worker.fetch(new Request(`${ENDPOINT}/docs`), ENV, CTX); + expect(docs.status).toBe(200); + expect(docs.headers.get("Content-Type")).toContain("text/html"); + expect(await docs.text()).toContain("@scalar/api-reference"); + + const spec = await worker.fetch(new Request(`${ENDPOINT}/openapi.yaml`), ENV, CTX); + expect(spec.status).toBe(200); + expect(spec.headers.get("Content-Type")).toContain("application/yaml"); + expect(new TextDecoder().decode(await spec.arrayBuffer())).toContain("openapi: 3.1.0"); + }); + + it("bypasses documentation routes when disabled", async () => { + const disabled = { ...ENV, ENABLE_DOCS: "false" }; + for (const path of ["/docs", "/openapi.yaml"]) { + const response = await worker.fetch(new Request(`${ENDPOINT}${path}`), disabled, CTX); + expect(response.status).toBe(403); + } + }); + + it("does not claim /docs when docs is a configured bucket", async () => { + const withDocsBucket = { ...ENV, ALLOWED_BUCKETS: "test-bucket,docs" }; + const response = await worker.fetch(new Request(`${ENDPOINT}/docs`), withDocsBucket, CTX); + expect(response.status).toBe(403); + expect(await response.text()).toContain("SignatureDoesNotMatch"); + }); +}); diff --git a/test/s3.test.ts b/test/s3.test.ts index c5127b4..b8fab68 100644 --- a/test/s3.test.ts +++ b/test/s3.test.ts @@ -56,7 +56,9 @@ class FakeDrive { if (url.pathname === "/drive/v3/files" && request.method === "POST") return this.createMetadata(await request.json>()); if (url.pathname.startsWith("/drive/v3/files/") && url.searchParams.get("alt") === "media") return this.download(url, request); if (url.pathname.startsWith("/drive/v3/files/") && request.method === "DELETE") { - this.files.delete(url.pathname.split("/").at(-1)!); + const fileId = url.pathname.split("/").at(-1); + if (!fileId) return new Response("Not Found", { status: 404 }); + this.files.delete(fileId); return new Response(null, { status: 204 }); } if (url.pathname.startsWith("/upload/drive/v3/files") && url.searchParams.get("uploadType") === "resumable") return this.initialize(url, request); @@ -121,7 +123,8 @@ class FakeDrive { } private async upload(url: URL, request: Request): Promise { - const id = url.pathname.split("/").at(-1)!; + const id = url.pathname.split("/").at(-1); + if (!id) return new Response(null, { status: 404 }); const session = this.sessions.get(id); if (!session) return new Response(null, { status: 404 }); if (request.method === "DELETE") { @@ -159,7 +162,9 @@ class FakeDrive { } private download(url: URL, request: Request): Response { - const file = this.files.get(url.pathname.split("/").at(-1)!); + const fileId = url.pathname.split("/").at(-1); + if (!fileId) return new Response(null, { status: 404 }); + const file = this.files.get(fileId); if (!file) return new Response(null, { status: 404 }); const range = request.headers.get("Range"); if (!range) return new Response(file.data, { headers: { "Content-Length": String(file.data.byteLength) } }); @@ -204,6 +209,18 @@ async function signed(path: string, init: RequestInit): Promise { }); } +async function presigned(path: string, init: RequestInit, options: { datetime?: string; accessKeyId?: string; expires?: string } = {}): Promise { + const url = new URL(`${ENDPOINT}${path}`); + url.searchParams.set("X-Amz-Expires", options.expires ?? "60"); + const aws = new AwsClient({ accessKeyId: options.accessKeyId ?? ENV.ACCESS_KEY, secretAccessKey: ENV.SECRET_KEY, region: ENV.REGION, service: "s3" }); + const bodyLength = typeof init.body === "string" ? new TextEncoder().encode(init.body).byteLength : init.body instanceof Uint8Array ? init.body.byteLength : undefined; + return aws.sign(url.toString(), { + ...init, + headers: { "x-amz-content-sha256": "UNSIGNED-PAYLOAD", ...(bodyLength === undefined ? {} : { "x-amz-decoded-content-length": String(bodyLength) }), ...init.headers }, + aws: { signQuery: true, ...(options.datetime ? { datetime: options.datetime } : {}) }, + }); +} + let drive: FakeDrive; beforeEach(async () => { @@ -226,7 +243,10 @@ describe("S3 compatibility", () => { const second = await worker.fetch(await signed("/test-bucket/file.txt", { method: "PUT", body: "second" }), ENV, CTX); expect(second.status).toBe(200); expect([...drive.files.values()].filter((file) => file.name === "file.txt")).toHaveLength(1); - expect(new TextDecoder().decode([...drive.files.values()].find((file) => file.name === "file.txt")!.data)).toBe("second"); + const stored = [...drive.files.values()].find((file) => file.name === "file.txt"); + expect(stored).toBeDefined(); + if (!stored) throw new Error("Overwritten object was not stored"); + expect(new TextDecoder().decode(stored.data)).toBe("second"); }); it("sets Last-Modified on GET and HEAD from Drive's modifiedTime", async () => { @@ -264,6 +284,33 @@ describe("S3 compatibility", () => { expect(response.status).toBe(200); }); + it("rejects expired presigned URLs and accepts unexpired ones", async () => { + const expired = await worker.fetch(await presigned("/test-bucket/file.txt", { method: "GET" }, { datetime: "20200101T000000Z", expires: "60" }), ENV, CTX); + expect(expired.status).toBe(403); + expect(await expired.text()).toContain("AccessDenied"); + }); + + it("accepts an unexpired presigned URL", async () => { + await worker.fetch(await signed("/test-bucket/presigned.txt", { method: "PUT", body: "hello" }), ENV, CTX); + const response = await worker.fetch(await presigned("/test-bucket/presigned.txt", { method: "GET" }), ENV, CTX); + expect(response.status).toBe(200); + expect(await response.text()).toBe("hello"); + }); + + it("rejects a Credential access key that does not match ACCESS_KEY", async () => { + const response = await worker.fetch(await presigned("/test-bucket/file.txt", { method: "GET" }, { accessKeyId: "unexpected-access-key" }), ENV, CTX); + expect(response.status).toBe(403); + expect(await response.text()).toContain("AccessDenied"); + }); + + it("rejects header-authenticated requests outside the 15-minute clock skew", async () => { + const aws = new AwsClient({ accessKeyId: ENV.ACCESS_KEY, secretAccessKey: ENV.SECRET_KEY, region: ENV.REGION, service: "s3" }); + const request = await aws.sign(`${ENDPOINT}/test-bucket/file.txt`, { method: "GET", aws: { datetime: "20200101T000000Z" } }); + const response = await worker.fetch(request, ENV, CTX); + expect(response.status).toBe(403); + expect(await response.text()).toContain("RequestTimeTooSkewed"); + }); + it("decodes both aws-chunked framing variants across arbitrary boundaries", async () => { const payload = bytes(70_013); for (const trailer of [true, false]) { @@ -282,20 +329,28 @@ describe("S3 compatibility", () => { const source = bytes(1_500_123); const create = await worker.fetch(await signed("/test-bucket/big.bin?uploads", { method: "POST", headers: { "Content-Type": "application/octet-stream" } }), ENV, CTX); expect(create.status).toBe(200); - const uploadId = /([^<]+)<\/UploadId>/.exec(await create.text())![1]; + const uploadId = /([^<]+)<\/UploadId>/.exec(await create.text())?.[1]; + expect(uploadId).toBeDefined(); + if (!uploadId) throw new Error("Multipart initiation did not return an upload ID"); const completed: Array<{ partNumber: number; etag: string }> = []; for (let index = 0, offset = 0; offset < source.byteLength; index++) { const end = Math.min(source.byteLength, offset + 500_000); const part = await worker.fetch(await signed(`/test-bucket/big.bin?partNumber=${index + 1}&uploadId=${encodeURIComponent(uploadId)}`, { method: "PUT", body: source.slice(offset, end) }), ENV, CTX); expect(part.status).toBe(200); - completed.push({ partNumber: index + 1, etag: part.headers.get("ETag")!.replaceAll('"', "") }); + const partEtag = part.headers.get("ETag"); + expect(partEtag).toBeDefined(); + if (!partEtag) throw new Error(`Multipart part ${index + 1} did not return an ETag`); + completed.push({ partNumber: index + 1, etag: partEtag.replaceAll('"', "") }); offset = end; } const xml = `${completed.map((part) => `${part.partNumber}"${part.etag}"`).join("")}`; const result = await worker.fetch(await signed(`/test-bucket/big.bin?uploadId=${encodeURIComponent(uploadId)}`, { method: "POST", body: xml }), ENV, CTX); expect(result.status).toBe(200); expect(await result.text()).toMatch(/"[0-9a-f]{32}"<\/ETag>/); - const stored = [...drive.files.values()].find((file) => file.name === "big.bin")!.data; + const storedFile = [...drive.files.values()].find((file) => file.name === "big.bin"); + expect(storedFile).toBeDefined(); + if (!storedFile) throw new Error("Completed multipart object was not stored"); + const stored = storedFile.data; expect(stored.byteLength).toBe(source.byteLength); expect(fakeMd5(stored)).toBe(fakeMd5(source)); }); @@ -317,13 +372,13 @@ describe("S3 compatibility", () => { CTX, ); expect(response.status).toBe(200); - expect([...drive.files.values()].find((file) => file.name === "chunked.bin")!.data).toEqual(source); + expect([...drive.files.values()].find((file) => file.name === "chunked.bin")?.data).toEqual(source); }); it("supports an empty PutObject", async () => { const response = await worker.fetch(await signed("/test-bucket/empty", { method: "PUT", body: new Uint8Array() }), ENV, CTX); expect(response.status).toBe(200); - expect([...drive.files.values()].find((file) => file.name === "empty")!.data.byteLength).toBe(0); + expect([...drive.files.values()].find((file) => file.name === "empty")?.data.byteLength).toBe(0); }); it("lists nested keys under a prefix, as CommonPrefixes with a delimiter and recursively without one", async () => { diff --git a/vitest.config.mts b/vitest.config.mts index bc13d45..0ff7d65 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -16,6 +16,8 @@ export default defineConfig({ ALLOWED_BUCKETS: "test-bucket,empty-bucket,my-bucket", ALLOW_MULTIPART: "true", ETAG_STYLE: "md5", + CORS_ALLOWED_ORIGINS: "http://localhost:5173", + ENABLE_DOCS: "true", }, }, }), diff --git a/worker-configuration.d.ts b/worker-configuration.d.ts index 8b68580..0c1063b 100644 --- a/worker-configuration.d.ts +++ b/worker-configuration.d.ts @@ -1,22 +1,27 @@ /* eslint-disable */ -// Generated by Wrangler by running `wrangler types` (hash: f1715efaed926a6241da69b62ea21ad8) -// Runtime types generated with workerd@1.20260804.1 2025-09-27 +// Generated by Wrangler by running `wrangler types` (hash: 292aeeaad14a692de2ecd641e28c3a4c) +// Runtime types generated with workerd@1.20260811.1 2025-09-27 interface __BaseEnv_Env { - AUTH_KV: KVNamespace; - FOLDER_CACHE: KVNamespace; - ALLOW_MULTIPART: "false"; - ETAG_STYLE: "md5"; - MPU: DurableObjectNamespace; + AUTH_KV: KVNamespace; + FOLDER_CACHE: KVNamespace; + ALLOW_MULTIPART: "true"; + ETAG_STYLE: "md5"; + CORS_ALLOWED_ORIGINS: ""; + ENABLE_DOCS: "true"; + MPU: DurableObjectNamespace; } declare namespace Cloudflare { - interface GlobalProps { - mainModule: typeof import("./src/index"); - durableNamespaces: "MultipartUploadDO"; - } - interface Env extends __BaseEnv_Env {} + interface GlobalProps { + mainModule: typeof import("./src/index"); + durableNamespaces: "MultipartUploadDO"; + } + interface Env extends __BaseEnv_Env {} } interface Env extends __BaseEnv_Env {} - +declare module "docs/openapi.yaml" { + const value: string; + export default value; +} // Begin runtime types /*! ***************************************************************************** Copyright (c) Cloudflare. All rights reserved. @@ -105,7 +110,7 @@ declare abstract class WorkerGlobalScope extends EventTarget): Promise; declare const self: ServiceWorkerGlobalScope; /** - * The Web Crypto API provides a set of low-level functions for common cryptographic tasks. - * The Workers runtime implements the full surface of this API, but with some differences in - * the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms) - * compared to those implemented in most browsers. - * - * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/) - */ +* The Web Crypto API provides a set of low-level functions for common cryptographic tasks. +* The Workers runtime implements the full surface of this API, but with some differences in +* the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms) +* compared to those implemented in most browsers. +* +* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/) +*/ declare const crypto: Crypto; /** - * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache. - * - * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/) - */ +* The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache. +* +* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/) +*/ declare const caches: CacheStorage; declare const scheduler: Scheduler; /** - * The Workers runtime supports a subset of the Performance API, used to measure timing and performance, - * as well as timing of subrequests and other operations. - * - * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/) - */ +* The Workers runtime supports a subset of the Performance API, used to measure timing and performance, +* as well as timing of subrequests and other operations. +* +* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/) +*/ declare const performance: Performance; declare const Cloudflare: Cloudflare; declare const origin: string; declare const navigator: Navigator; -type TestController = {}; +interface TestController { +} interface ExecutionContext { waitUntil(promise: Promise): void; passThroughOnException(): void; @@ -440,6 +446,7 @@ interface ExecutionContext { cache?: CacheContext; readonly access?: CloudflareAccessContext; tracing: Tracing; + abort(reason?: any): void; } type ExportedHandlerFetchHandler = (request: Request>, env: Env, ctx: ExecutionContext) => Response | Promise; type ExportedHandlerConnectHandler = (socket: Socket, env: Env, ctx: ExecutionContext) => void | Promise; @@ -538,7 +545,8 @@ interface DurableObjectNamespaceGetDurableObjectOptions { locationHint?: DurableObjectLocationHint; routingMode?: DurableObjectRoutingMode; } -type DurableObjectClass<_T extends Rpc.DurableObjectBranded | undefined = undefined> = {}; +interface DurableObjectClass<_T extends Rpc.DurableObjectBranded | undefined = undefined> { +} interface DurableObjectState { waitUntil(promise: Promise): void; readonly props: Props; @@ -996,10 +1004,10 @@ interface FileOptions { lastModified?: number; } /** - * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache. - * - * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/) - */ +* The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache. +* +* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/) +*/ declare abstract class CacheStorage { /** * The **`open()`** method of the the Cache object matching the `cacheName`. @@ -1010,10 +1018,10 @@ declare abstract class CacheStorage { readonly default: Cache; } /** - * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache. - * - * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/) - */ +* The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache. +* +* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/) +*/ declare abstract class Cache { /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#delete) */ delete(request: RequestInfo | URL, options?: CacheQueryOptions): Promise; @@ -1026,13 +1034,13 @@ interface CacheQueryOptions { ignoreMethod?: boolean; } /** - * The Web Crypto API provides a set of low-level functions for common cryptographic tasks. - * The Workers runtime implements the full surface of this API, but with some differences in - * the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms) - * compared to those implemented in most browsers. - * - * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/) - */ +* The Web Crypto API provides a set of low-level functions for common cryptographic tasks. +* The Workers runtime implements the full surface of this API, but with some differences in +* the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms) +* compared to those implemented in most browsers. +* +* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/) +*/ declare abstract class Crypto { /** * The **`Crypto.subtle`** read-only property returns a cryptographic operations. @@ -1200,26 +1208,26 @@ interface RsaOtherPrimesInfo { } interface SubtleCryptoDeriveKeyAlgorithm { name: string; - salt?: ArrayBuffer | ArrayBufferView; + salt?: (ArrayBuffer | ArrayBufferView); iterations?: number; - hash?: string | SubtleCryptoHashAlgorithm; + hash?: (string | SubtleCryptoHashAlgorithm); $public?: CryptoKey; - info?: ArrayBuffer | ArrayBufferView; + info?: (ArrayBuffer | ArrayBufferView); } interface SubtleCryptoEncryptAlgorithm { name: string; - iv?: ArrayBuffer | ArrayBufferView; - additionalData?: ArrayBuffer | ArrayBufferView; + iv?: (ArrayBuffer | ArrayBufferView); + additionalData?: (ArrayBuffer | ArrayBufferView); tagLength?: number; - counter?: ArrayBuffer | ArrayBufferView; + counter?: (ArrayBuffer | ArrayBufferView); length?: number; - label?: ArrayBuffer | ArrayBufferView; + label?: (ArrayBuffer | ArrayBufferView); } interface SubtleCryptoGenerateKeyAlgorithm { name: string; - hash?: string | SubtleCryptoHashAlgorithm; + hash?: (string | SubtleCryptoHashAlgorithm); modulusLength?: number; - publicExponent?: ArrayBuffer | ArrayBufferView; + publicExponent?: (ArrayBuffer | ArrayBufferView); length?: number; namedCurve?: string; } @@ -1228,14 +1236,14 @@ interface SubtleCryptoHashAlgorithm { } interface SubtleCryptoImportKeyAlgorithm { name: string; - hash?: string | SubtleCryptoHashAlgorithm; + hash?: (string | SubtleCryptoHashAlgorithm); length?: number; namedCurve?: string; compressed?: boolean; } interface SubtleCryptoSignAlgorithm { name: string; - hash?: string | SubtleCryptoHashAlgorithm; + hash?: (string | SubtleCryptoHashAlgorithm); dataLength?: number; saltLength?: number; } @@ -1284,7 +1292,7 @@ declare class TextDecoder { * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder/decode) */ - decode(input?: ArrayBuffer | ArrayBufferView, options?: TextDecoderDecodeOptions): string; + decode(input?: (ArrayBuffer | ArrayBufferView), options?: TextDecoderDecodeOptions): string; get encoding(): string; get fatal(): boolean; get ignoreBOM(): boolean; @@ -1494,13 +1502,19 @@ declare class FormData { */ set(name: string, value: Blob, filename?: string): void; /* Returns an array of key, value pairs for every entry in the list. */ - entries(): IterableIterator<[key: string, value: File | string]>; + entries(): IterableIterator<[ + key: string, + value: File | string + ]>; /* Returns a list of keys in the list. */ keys(): IterableIterator; /* Returns a list of values in the list. */ - values(): IterableIterator; + values(): IterableIterator<(File | string)>; forEach(callback: (this: This, value: File | string, key: string, parent: FormData) => void, thisArg?: This): void; - [Symbol.iterator](): IterableIterator<[key: string, value: File | string]>; + [Symbol.iterator](): IterableIterator<[ + key: string, + value: File | string + ]>; } interface ContentOptions { html?: boolean; @@ -1639,12 +1653,18 @@ declare class Headers { delete(name: string): void; forEach(callback: (this: This, value: string, key: string, parent: Headers) => void, thisArg?: This): void; /* Returns an iterator allowing to go through all key/value pairs contained in this object. */ - entries(): IterableIterator<[key: string, value: string]>; + entries(): IterableIterator<[ + key: string, + value: string + ]>; /* Returns an iterator allowing to go through all keys of the key/value pairs contained in this object. */ keys(): IterableIterator; /* Returns an iterator allowing to go through all values of the key/value pairs contained in this object. */ values(): IterableIterator; - [Symbol.iterator](): IterableIterator<[key: string, value: string]>; + [Symbol.iterator](): IterableIterator<[ + key: string, + value: string + ]>; } type BodyInit = ReadableStream | string | ArrayBuffer | ArrayBufferView | Blob | URLSearchParams | FormData; declare abstract class Body { @@ -1675,7 +1695,7 @@ declare var Response: { new (body?: BodyInit | null, init?: ResponseInit): Response; error(): Response; redirect(url: string, status?: number): Response; - json(any: any, maybeInit?: ResponseInit | Response): Response; + json(any: any, maybeInit?: (ResponseInit | Response)): Response; }; /** * The **`Response`** interface of the Fetch API represents the response to a request. @@ -1739,7 +1759,7 @@ interface ResponseInit { statusText?: string; headers?: HeadersInit; cf?: any; - webSocket?: WebSocket | null; + webSocket?: (WebSocket | null); encodeBody?: "automatic" | "manual"; } type RequestInfo> = Request | string; @@ -1824,25 +1844,17 @@ interface RequestInit { body?: BodyInit | null; /* A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect. */ redirect?: string; - fetcher?: Fetcher | null; + fetcher?: (Fetcher | null); cf?: Cf; /* A string indicating how the request will interact with the browser's cache to set request's cache. */ cache?: "no-store" | "no-cache"; /* A cryptographic hash of the resource to be fetched by request. Sets request's integrity. */ integrity?: string; /* An AbortSignal to set request's signal. */ - signal?: AbortSignal | null; + signal?: (AbortSignal | null); encodeResponseBody?: "automatic" | "manual"; } -type Service Rpc.WorkerEntrypointBranded) | Rpc.WorkerEntrypointBranded | ExportedHandler | undefined = undefined> = T extends new ( - ...args: any[] -) => Rpc.WorkerEntrypointBranded - ? Fetcher> - : T extends Rpc.WorkerEntrypointBranded - ? Fetcher - : T extends Exclude - ? never - : Fetcher; +type Service Rpc.WorkerEntrypointBranded) | Rpc.WorkerEntrypointBranded | ExportedHandler | undefined = undefined> = T extends new (...args: any[]) => Rpc.WorkerEntrypointBranded ? Fetcher> : T extends Rpc.WorkerEntrypointBranded ? Fetcher : T extends Exclude ? never : Fetcher; type Fetcher = (T extends Rpc.EntrypointBranded ? Rpc.Provider : unknown) & { fetch(input: RequestInfo | URL, init?: RequestInit): Promise; connect(address: SocketAddress | string, options?: SocketOptions): Socket; @@ -1852,18 +1864,16 @@ interface KVNamespaceListKey { expiration?: number; metadata?: Metadata; } -type KVNamespaceListResult = - | { - list_complete: false; - keys: KVNamespaceListKey[]; - cursor: string; - cacheStatus: string | null; - } - | { - list_complete: true; - keys: KVNamespaceListKey[]; - cacheStatus: string | null; - }; +type KVNamespaceListResult = { + list_complete: false; + keys: KVNamespaceListKey[]; + cursor: string; + cacheStatus: string | null; +} | { + list_complete: true; + keys: KVNamespaceListKey[]; + cacheStatus: string | null; +}; interface KVNamespace { get(key: Key, options?: Partial>): Promise; get(key: Key, type: "text"): Promise; @@ -1899,8 +1909,8 @@ interface KVNamespace { } interface KVNamespaceListOptions { limit?: number; - prefix?: string | null; - cursor?: string | null; + prefix?: (string | null); + cursor?: (string | null); } interface KVNamespaceGetOptions { type: Type; @@ -1909,7 +1919,7 @@ interface KVNamespaceGetOptions { interface KVNamespacePutOptions { expiration?: number; expirationTtl?: number; - metadata?: any | null; + metadata?: (any | null); } interface KVNamespaceGetWithMetadataResult { value: Value | null; @@ -2011,20 +2021,13 @@ interface R2ListOptions { } interface R2Bucket { head(key: string): Promise; - get( - key: string, - options: R2GetOptions & { - onlyIf: R2Conditional | Headers; - }, - ): Promise; + get(key: string, options: R2GetOptions & { + onlyIf: R2Conditional | Headers; + }): Promise; get(key: string, options?: R2GetOptions): Promise; - put( - key: string, - value: ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob, - options?: R2PutOptions & { - onlyIf: R2Conditional | Headers; - }, - ): Promise; + put(key: string, value: ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob, options?: R2PutOptions & { + onlyIf: R2Conditional | Headers; + }): Promise; put(key: string, value: ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob, options?: R2PutOptions): Promise; createMultipartUpload(key: string, options?: R2MultipartOptions): Promise; resumeMultipartUpload(key: string, uploadId: string): R2MultipartUpload; @@ -2066,18 +2069,15 @@ interface R2ObjectBody extends R2Object { json(): Promise; blob(): Promise; } -type R2Range = - | { - offset: number; - length?: number; - } - | { - offset?: number; - length: number; - } - | { - suffix: number; - }; +type R2Range = { + offset: number; + length?: number; +} | { + offset?: number; + length: number; +} | { + suffix: number; +}; interface R2Conditional { etagMatches?: string; etagDoesNotMatch?: string; @@ -2086,27 +2086,27 @@ interface R2Conditional { secondsGranularity?: boolean; } interface R2GetOptions { - onlyIf?: R2Conditional | Headers; - range?: R2Range | Headers; - ssecKey?: ArrayBuffer | string; + onlyIf?: (R2Conditional | Headers); + range?: (R2Range | Headers); + ssecKey?: (ArrayBuffer | string); } interface R2PutOptions { - onlyIf?: R2Conditional | Headers; - httpMetadata?: R2HTTPMetadata | Headers; + onlyIf?: (R2Conditional | Headers); + httpMetadata?: (R2HTTPMetadata | Headers); customMetadata?: Record; - md5?: (ArrayBuffer | ArrayBufferView) | string; - sha1?: (ArrayBuffer | ArrayBufferView) | string; - sha256?: (ArrayBuffer | ArrayBufferView) | string; - sha384?: (ArrayBuffer | ArrayBufferView) | string; - sha512?: (ArrayBuffer | ArrayBufferView) | string; + md5?: ((ArrayBuffer | ArrayBufferView) | string); + sha1?: ((ArrayBuffer | ArrayBufferView) | string); + sha256?: ((ArrayBuffer | ArrayBufferView) | string); + sha384?: ((ArrayBuffer | ArrayBufferView) | string); + sha512?: ((ArrayBuffer | ArrayBufferView) | string); storageClass?: string; - ssecKey?: ArrayBuffer | string; + ssecKey?: (ArrayBuffer | string); } interface R2MultipartOptions { - httpMetadata?: R2HTTPMetadata | Headers; + httpMetadata?: (R2HTTPMetadata | Headers); customMetadata?: Record; storageClass?: string; - ssecKey?: ArrayBuffer | string; + ssecKey?: (ArrayBuffer | string); } interface R2Checksums { readonly md5?: ArrayBuffer; @@ -2134,17 +2134,14 @@ interface R2HTTPMetadata { type R2Objects = { objects: R2Object[]; delimitedPrefixes: string[]; -} & ( - | { - truncated: true; - cursor: string; - } - | { - truncated: false; - } -); +} & ({ + truncated: true; + cursor: string; +} | { + truncated: false; +}); interface R2UploadPartOptions { - ssecKey?: ArrayBuffer | string; + ssecKey?: (ArrayBuffer | string); } declare abstract class ScheduledEvent extends ExtendableEvent { readonly scheduledTime: number; @@ -2157,7 +2154,7 @@ interface ScheduledController { noRetry(): void; } interface QueuingStrategy { - highWaterMark?: number | bigint; + highWaterMark?: (number | bigint); size?: (chunk: T) => number | bigint; } interface UnderlyingSink { @@ -2179,7 +2176,7 @@ interface UnderlyingSource { start?: (controller: ReadableStreamDefaultController) => void | Promise; pull?: (controller: ReadableStreamDefaultController) => void | Promise; cancel?: (reason: any) => void | Promise; - expectedLength?: number | bigint; + expectedLength?: (number | bigint); } interface Transformer { readableType?: string; @@ -2213,15 +2210,13 @@ interface StreamPipeOptions { preventClose?: boolean; signal?: AbortSignal; } -type ReadableStreamReadResult = - | { - done: false; - value: R; - } - | { - done: true; - value?: undefined; - }; +type ReadableStreamReadResult = { + done: false; + value: R; +} | { + done: true; + value?: undefined; +}; /** * The `ReadableStream` interface of the Streams API represents a readable stream of byte data. * @@ -2269,7 +2264,10 @@ interface ReadableStream { * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/tee) */ - tee(): [ReadableStream, ReadableStream]; + tee(): [ + ReadableStream, + ReadableStream + ]; values(options?: ReadableStreamValuesOptions): AsyncIterableIterator; [Symbol.asyncIterator](options?: ReadableStreamValuesOptions): AsyncIterableIterator; } @@ -2601,7 +2599,7 @@ declare class IdentityTransformStream extends TransformStream> | Record | string); + constructor(init?: (Iterable> | Record | string)); /** * The **`size`** read-only property of the URLSearchParams interface indicates the total number of search parameter entries. * @@ -3026,7 +3032,10 @@ declare class URLSearchParams { */ sort(): void; /* Returns an array of key, value pairs for every entry in the search params. */ - entries(): IterableIterator<[key: string, value: string]>; + entries(): IterableIterator<[ + key: string, + value: string + ]>; /* Returns a list of keys in the search params. */ keys(): IterableIterator; /* Returns a list of values in the search params. */ @@ -3034,10 +3043,13 @@ declare class URLSearchParams { forEach(callback: (this: This, value: string, key: string, parent: URLSearchParams) => void, thisArg?: This): void; /*function toString() { [native code] }*/ toString(): string; - [Symbol.iterator](): IterableIterator<[key: string, value: string]>; + [Symbol.iterator](): IterableIterator<[ + key: string, + value: string + ]>; } declare class URLPattern { - constructor(input?: string | URLPatternInit, baseURL?: string | URLPatternOptions, patternOptions?: URLPatternOptions); + constructor(input?: (string | URLPatternInit), baseURL?: (string | URLPatternOptions), patternOptions?: URLPatternOptions); get protocol(): string; get username(): string; get password(): string; @@ -3047,8 +3059,8 @@ declare class URLPattern { get search(): string; get hash(): string; get hasRegExpGroups(): boolean; - test(input?: string | URLPatternInit, baseURL?: string): boolean; - exec(input?: string | URLPatternInit, baseURL?: string): URLPatternResult | null; + test(input?: (string | URLPatternInit), baseURL?: string): boolean; + exec(input?: (string | URLPatternInit), baseURL?: string): URLPatternResult | null; } interface URLPatternInit { protocol?: string; @@ -3123,7 +3135,7 @@ type WebSocketEventMap = { */ declare var WebSocket: { prototype: WebSocket; - new (url: string, protocols?: string[] | string): WebSocket; + new (url: string, protocols?: (string[] | string)): WebSocket; readonly READY_STATE_CONNECTING: number; readonly CONNECTING: number; readonly READY_STATE_OPEN: number; @@ -3207,18 +3219,17 @@ interface SqlStorage { Cursor: typeof SqlStorageCursor; Statement: typeof SqlStorageStatement; } -declare abstract class SqlStorageStatement {} +declare abstract class SqlStorageStatement { +} type SqlStorageValue = ArrayBuffer | string | number | null; declare abstract class SqlStorageCursor> { - next(): - | { - done?: false; - value: T; - } - | { - done: true; - value?: never; - }; + next(): { + done?: false; + value: T; + } | { + done: true; + value?: never; + }; toArray(): T[]; one(): T; raw(): IterableIterator; @@ -3240,7 +3251,7 @@ interface Socket { interface SocketOptions { secureTransport?: string; allowHalfOpen: boolean; - highWaterMark?: number | bigint; + highWaterMark?: (number | bigint); } interface SocketAddress { hostname: string; @@ -3314,18 +3325,26 @@ interface ContainerExecOptions { cwd?: string; env?: Record; user?: string; + signal?: AbortSignal; + pty?: boolean | ContainerExecPtyOptions; stdin?: ReadableStream | "pipe"; stdout?: "pipe" | "ignore"; stderr?: "pipe" | "ignore" | "combined"; } +interface ContainerExecPtyOptions { + cols?: number; + rows?: number; +} interface ExecProcess { readonly stdin: WritableStream | null; readonly stdout: ReadableStream | null; readonly stderr: ReadableStream | null; readonly pid: number; + readonly isPty: boolean; readonly exitCode: Promise; output(): Promise; kill(signal?: number): void; + resize(cols: number, rows: number): void; } interface Container { get running(): boolean; @@ -3372,6 +3391,11 @@ interface ContainerStartupOptions { directorySnapshots?: ContainerDirectorySnapshotRestoreParams[]; containerSnapshot?: ContainerSnapshot; } +interface ContainerStartResources { + vcpu: number; + memoryMib: number; + diskMb: number; +} /** * The **`MessagePort`** interface of the Channel Messaging API represents one of the two ports of a MessageChannel, allowing messages to be sent from one port and listening out for them arriving at the other. * @@ -3383,7 +3407,7 @@ declare abstract class MessagePort extends EventTarget { * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/postMessage) */ - postMessage(data?: any, options?: any[] | MessagePortPostMessageOptions): void; + postMessage(data?: any, options?: (any[] | MessagePortPostMessageOptions)): void; /** * The **`close()`** method of the MessagePort interface disconnects the port, so it is no longer active. * @@ -3422,24 +3446,27 @@ declare class MessageChannel { interface MessagePortPostMessageOptions { transfer?: any[]; } -type LoopbackForExport Rpc.EntrypointBranded) | ExportedHandler | undefined = undefined> = T extends new ( - ...args: any[] -) => Rpc.WorkerEntrypointBranded - ? LoopbackServiceStub> - : T extends new ( - ...args: any[] - ) => Rpc.DurableObjectBranded - ? LoopbackDurableObjectClass> - : T extends ExportedHandler - ? LoopbackServiceStub - : undefined; -type LoopbackServiceStub = Fetcher & (T extends CloudflareWorkersModule.WorkerEntrypoint ? (opts: { props?: Props }) => Fetcher : (opts: { props?: any }) => Fetcher); -type LoopbackDurableObjectClass = DurableObjectClass & (T extends CloudflareWorkersModule.DurableObject ? (opts: { props?: Props }) => DurableObjectClass : (opts: { props?: any }) => DurableObjectClass); -interface LoopbackDurableObjectNamespace extends DurableObjectNamespace {} -interface LoopbackColoLocalActorNamespace extends ColoLocalActorNamespace {} +type LoopbackForExport Rpc.EntrypointBranded) | ExportedHandler | undefined = undefined> = T extends new (...args: any[]) => Rpc.WorkerEntrypointBranded ? LoopbackServiceStub> : T extends new (...args: any[]) => Rpc.DurableObjectBranded ? LoopbackDurableObjectClass> : T extends ExportedHandler ? LoopbackServiceStub : undefined; +type LoopbackServiceStub = Fetcher & (T extends CloudflareWorkersModule.WorkerEntrypoint ? (opts: { + props?: Props; +}) => Fetcher : (opts: { + props?: any; +}) => Fetcher); +type LoopbackDurableObjectClass = DurableObjectClass & (T extends CloudflareWorkersModule.DurableObject ? (opts: { + props?: Props; +}) => DurableObjectClass : (opts: { + props?: any; +}) => DurableObjectClass); +interface LoopbackDurableObjectNamespace extends DurableObjectNamespace { +} +interface LoopbackColoLocalActorNamespace extends ColoLocalActorNamespace { +} interface SyncKvStorage { get(key: string): T | undefined; - list(options?: SyncKvListOptions): Iterable<[string, T]>; + list(options?: SyncKvListOptions): Iterable<[ + string, + T + ]>; put(key: string, value: T): void; delete(key: string): boolean; } @@ -3480,7 +3507,7 @@ interface WorkerLoaderWorkerCode { mainModule: string; modules: Record; env?: any; - globalOutbound?: Fetcher | null; + globalOutbound?: (Fetcher | null); tails?: Fetcher[]; streamingTails?: Fetcher[]; } @@ -3489,11 +3516,11 @@ interface workerdResourceLimits { subRequests?: number; } /** - * The Workers runtime supports a subset of the Performance API, used to measure timing and performance, - * as well as timing of subrequests and other operations. - * - * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/) - */ +* The Workers runtime supports a subset of the Performance API, used to measure timing and performance, +* as well as timing of subrequests and other operations. +* +* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/) +*/ declare abstract class Performance { /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancetimeorigin) */ get timeOrigin(): number; @@ -3509,11 +3536,13 @@ declare abstract class Performance { interface Tracing { enterSpan(name: string, callback: (span: Span, ...args: A) => T, ...args: A): T; startActiveSpan(name: string, callback: (span: Span, ...args: A) => T, ...args: A): T; + startSpan(name: string): Span; Span: typeof Span; } declare abstract class Span { get isTraced(): boolean; - setAttribute(key: string, value?: boolean | number | string): void; + setAttribute(key: string, value: boolean | number | string): this; + setAttributes(attributes: Record): this; end(): void; } /** @@ -3785,12 +3814,14 @@ declare abstract class AgentMemoryNamespace { deleteProfile(profileName: string): Promise; } // ============ AI Search Error Interfaces ============ -interface AiSearchInternalError extends Error {} -interface AiSearchNotFoundError extends Error {} +interface AiSearchInternalError extends Error { +} +interface AiSearchNotFoundError extends Error { +} // ============ AI Search Common Types ============ /** A single message in a conversation-style search or chat request. */ type AiSearchMessage = { - role: "system" | "developer" | "user" | "assistant" | "tool"; + role: 'system' | 'developer' | 'user' | 'assistant' | 'tool'; content: string | null; }; /** @@ -3800,11 +3831,11 @@ type AiSearchMessage = { type AiSearchOptions = { retrieval?: { /** Which retrieval backend to use. Defaults to the instance's configured index_method. */ - retrieval_type?: "vector" | "keyword" | "hybrid"; + retrieval_type?: 'vector' | 'keyword' | 'hybrid'; /** Fusion method for combining vector + keyword results. */ - fusion_method?: "max" | "rrf"; + fusion_method?: 'max' | 'rrf'; /** How keyword terms are combined: "and" = all terms must match, "or" = any term matches. */ - keyword_match_mode?: "and" | "or"; + keyword_match_mode?: 'and' | 'or'; /** Minimum similarity score (0-1) for a result to be included. Default 0.4. */ match_threshold?: number; /** Maximum number of results to return (1-50). Default 10. */ @@ -3820,7 +3851,7 @@ type AiSearchOptions = { /** Boost results by metadata field values. Max 3 entries. */ boost_by?: Array<{ field: string; - direction?: "asc" | "desc" | "exists" | "not_exists"; + direction?: 'asc' | 'desc' | 'exists' | 'not_exists'; }>; [key: string]: unknown; }; @@ -3839,7 +3870,7 @@ type AiSearchOptions = { }; cache?: { enabled?: boolean; - cache_threshold?: "super_strict_match" | "close_enough" | "flexible_friend" | "anything_goes"; + cache_threshold?: 'super_strict_match' | 'close_enough' | 'flexible_friend' | 'anything_goes'; }; [key: string]: unknown; }; @@ -3848,19 +3879,17 @@ type AiSearchOptions = { * Request body for single-instance search. * Exactly one of `query` or `messages` must be provided. */ -type AiSearchSearchRequest = - | { - /** Simple query string. */ - query: string; - messages?: never; - ai_search_options?: AiSearchOptions; - } - | { - query?: never; - /** Conversation-style input. At least one user message with non-empty content is required. */ - messages: AiSearchMessage[]; - ai_search_options?: AiSearchOptions; - }; +type AiSearchSearchRequest = { + /** Simple query string. */ + query: string; + messages?: never; + ai_search_options?: AiSearchOptions; +} | { + query?: never; + /** Conversation-style input. At least one user message with non-empty content is required. */ + messages: AiSearchMessage[]; + ai_search_options?: AiSearchOptions; +}; type AiSearchChatCompletionsRequest = { messages: AiSearchMessage[]; model?: string; @@ -3879,21 +3908,19 @@ type AiSearchMultiSearchOptions = AiSearchOptions & { * `ai_search_options` is required and must include `instance_ids`. * Exactly one of `query` or `messages` must be provided. */ -type AiSearchMultiSearchRequest = - | { - /** Simple query string. */ - query: string; - messages?: never; - ai_search_options: AiSearchMultiSearchOptions; - } - | { - query?: never; - /** Conversation-style input. */ - messages: AiSearchMessage[]; - ai_search_options: AiSearchMultiSearchOptions; - }; +type AiSearchMultiSearchRequest = { + /** Simple query string. */ + query: string; + messages?: never; + ai_search_options: AiSearchMultiSearchOptions; +} | { + query?: never; + /** Conversation-style input. */ + messages: AiSearchMessage[]; + ai_search_options: AiSearchMultiSearchOptions; +}; /** A search result chunk tagged with the instance it originated from. */ -type AiSearchMultiSearchChunk = AiSearchSearchResponse["chunks"][number] & { +type AiSearchMultiSearchChunk = AiSearchSearchResponse['chunks'][number] & { instance_id: string; }; /** Describes a per-instance error during a multi-instance operation. */ @@ -3908,11 +3935,11 @@ type AiSearchMultiSearchResponse = { errors?: AiSearchMultiSearchError[]; }; /** Request for chat completions across multiple instances within a namespace. `ai_search_options` is required and must include `instance_ids`. */ -type AiSearchMultiChatCompletionsRequest = Omit & { +type AiSearchMultiChatCompletionsRequest = Omit & { ai_search_options: AiSearchMultiSearchOptions; }; /** Response from multi-instance chat completions, with chunks tagged by instance and optional partial-failure errors. */ -type AiSearchMultiChatCompletionsResponse = Omit & { +type AiSearchMultiChatCompletionsResponse = Omit & { chunks: AiSearchMultiSearchChunk[]; errors?: AiSearchMultiSearchError[]; }; @@ -3942,7 +3969,7 @@ type AiSearchSearchResponse = { /** Reranking model score */ reranking_score?: number; /** Fusion method used to combine results */ - fusion_method?: "rrf" | "max"; + fusion_method?: 'rrf' | 'max'; [key: string]: unknown; }; }>; @@ -3954,13 +3981,13 @@ type AiSearchChatCompletionsResponse = { choices: Array<{ index?: number; message: { - role: "system" | "developer" | "user" | "assistant" | "tool"; + role: 'system' | 'developer' | 'user' | 'assistant' | 'tool'; content: string | null; [key: string]: unknown; }; [key: string]: unknown; }>; - chunks: AiSearchSearchResponse["chunks"]; + chunks: AiSearchSearchResponse['chunks']; [key: string]: unknown; }; type AiSearchStatsResponse = { @@ -3987,7 +4014,7 @@ type AiSearchStatsResponse = { // ============ AI Search Instance Info Types ============ type AiSearchInstanceInfo = { id: string; - type?: "r2" | "web-crawler" | string; + type?: 'r2' | 'web-crawler' | string; source?: string; source_params?: unknown; paused?: boolean; @@ -4011,15 +4038,15 @@ type AiSearchInstanceInfo = { keyword?: boolean; }; /** Fusion method for combining vector and keyword results. */ - fusion_method?: "max" | "rrf"; + fusion_method?: 'max' | 'rrf'; indexing_options?: { - keyword_tokenizer?: "porter" | "trigram"; + keyword_tokenizer?: 'porter' | 'trigram'; } | null; retrieval_options?: { - keyword_match_mode?: "and" | "or"; + keyword_match_mode?: 'and' | 'or'; boost_by?: Array<{ field: string; - direction?: "asc" | "desc" | "exists" | "not_exists"; + direction?: 'asc' | 'desc' | 'exists' | 'not_exists'; }>; } | null; chunk?: boolean; @@ -4028,10 +4055,10 @@ type AiSearchInstanceInfo = { score_threshold?: number; max_num_results?: number; cache?: boolean; - cache_threshold?: "super_strict_match" | "close_enough" | "flexible_friend" | "anything_goes"; + cache_threshold?: 'super_strict_match' | 'close_enough' | 'flexible_friend' | 'anything_goes'; custom_metadata?: Array<{ field_name: string; - data_type: "text" | "number" | "boolean" | "datetime"; + data_type: 'text' | 'number' | 'boolean' | 'datetime'; }>; /** Sync interval in seconds. */ sync_interval?: 3600 | 7200 | 14400 | 21600 | 43200 | 86400; @@ -4045,9 +4072,9 @@ type AiSearchListInstancesParams = { /** Search instances by ID. */ search?: string; /** Field to sort by. */ - order_by?: "created_at"; + order_by?: 'created_at'; /** Sort direction. */ - order_by_direction?: "asc" | "desc"; + order_by_direction?: 'asc' | 'desc'; }; type AiSearchListResponse = { result: AiSearchInstanceInfo[]; @@ -4063,7 +4090,7 @@ type AiSearchConfig = { /** Instance ID (1-32 chars, pattern: ^[a-z0-9_]+(?:-[a-z0-9_]+)*$) */ id: string; /** Instance type. Omit to create with built-in storage. */ - type?: "r2" | "web-crawler" | string; + type?: 'r2' | 'web-crawler' | string; /** Source URL (required for web-crawler type). */ source?: string; source_params?: unknown; @@ -4086,15 +4113,15 @@ type AiSearchConfig = { keyword?: boolean; }; /** Fusion method for combining vector and keyword results. "rrf" = reciprocal rank fusion (default), "max" = maximum score. */ - fusion_method?: "max" | "rrf"; + fusion_method?: 'max' | 'rrf'; indexing_options?: { - keyword_tokenizer?: "porter" | "trigram"; + keyword_tokenizer?: 'porter' | 'trigram'; } | null; retrieval_options?: { - keyword_match_mode?: "and" | "or"; + keyword_match_mode?: 'and' | 'or'; boost_by?: Array<{ field: string; - direction?: "asc" | "desc" | "exists" | "not_exists"; + direction?: 'asc' | 'desc' | 'exists' | 'not_exists'; }>; } | null; chunk?: boolean; @@ -4105,10 +4132,10 @@ type AiSearchConfig = { max_num_results?: number; cache?: boolean; /** Similarity threshold for cache hits. Stricter = fewer cache hits but higher relevance. */ - cache_threshold?: "super_strict_match" | "close_enough" | "flexible_friend" | "anything_goes"; + cache_threshold?: 'super_strict_match' | 'close_enough' | 'flexible_friend' | 'anything_goes'; custom_metadata?: Array<{ field_name: string; - data_type: "text" | "number" | "boolean" | "datetime"; + data_type: 'text' | 'number' | 'boolean' | 'datetime'; }>; namespace?: string; /** Sync interval in seconds. 3600=1h, 7200=2h, 14400=4h, 21600=6h, 43200=12h, 86400=24h. */ @@ -4120,8 +4147,8 @@ type AiSearchConfig = { type AiSearchItemInfo = { id: string; key: string; - status: "completed" | "error" | "skipped" | "queued" | "running" | "outdated"; - next_action?: "INDEX" | "DELETE" | null; + status: 'completed' | 'error' | 'skipped' | 'queued' | 'running' | 'outdated'; + next_action?: 'INDEX' | 'DELETE' | null; error?: string; checksum?: string; namespace?: string; @@ -4148,13 +4175,20 @@ type AiSearchListItemsParams = { /** Search items by key name. */ search?: string; /** Sort order for results. */ - sort_by?: "status" | "modified_at"; + sort_by?: 'status' | 'modified_at'; /** Filter items by processing status. */ - status?: "queued" | "running" | "completed" | "error" | "skipped" | "outdated"; + status?: 'queued' | 'running' | 'completed' | 'error' | 'skipped' | 'outdated'; /** Filter items by source (e.g. "builtin" or "web-crawler:https://example.com"). */ source?: string; /** JSON-encoded Vectorize filter for metadata filtering. */ metadata_filter?: string; + /** Filter items by their unique ID. Returns at most one item. */ + item_id?: string; + /** + * Filter items by their exact key (object key / filename). Keys are unique + * per source, so combine with `source` to disambiguate across data sources. + */ + key?: string; }; type AiSearchListItemsResponse = { result: AiSearchItemInfo[]; @@ -4223,7 +4257,7 @@ type AiSearchItemChunksResponse = { // ============ AI Search Job Types ============ type AiSearchJobInfo = { id: string; - source: "user" | "schedule"; + source: 'user' | 'schedule'; description?: string; last_seen_at?: string; started_at?: string; @@ -4321,16 +4355,12 @@ declare abstract class AiSearchItems { * @param options Optional metadata and polling configuration. * @returns The item info after processing completes (or timeout). */ - uploadAndPoll( - name: string, - content: ReadableStream | Blob | string, - options?: AiSearchUploadItemOptions & { - /** Polling interval in milliseconds (default 1000). */ - pollIntervalMs?: number; - /** Maximum time to wait in milliseconds (default 30000). */ - timeoutMs?: number; - }, - ): Promise; + uploadAndPoll(name: string, content: ReadableStream | Blob | string, options?: AiSearchUploadItemOptions & { + /** Polling interval in milliseconds (default 1000). */ + pollIntervalMs?: number; + /** Maximum time to wait in milliseconds (default 30000). */ + timeoutMs?: number; + }): Promise; /** * Get an item by ID. * @param itemId The item identifier. @@ -4415,11 +4445,9 @@ declare abstract class AiSearchInstance { * @param params Chat completions request with stream: true. * @returns ReadableStream of server-sent events. */ - chatCompletions( - params: AiSearchChatCompletionsRequest & { - stream: true; - }, - ): Promise; + chatCompletions(params: AiSearchChatCompletionsRequest & { + stream: true; + }): Promise; /** * Generate chat completions with AI Search context. * @param params Chat completions request. @@ -4526,11 +4554,9 @@ declare abstract class AiSearchNamespace { * @param params Chat completions request with stream: true and required `ai_search_options.instance_ids`. * @returns ReadableStream of server-sent events. */ - chatCompletions( - params: AiSearchMultiChatCompletionsRequest & { - stream: true; - }, - ): Promise; + chatCompletions(params: AiSearchMultiChatCompletionsRequest & { + stream: true; + }): Promise; /** * Generate chat completions across multiple instances within the bound namespace. * Fans out to the specified instance_ids, merges context, and generates a response. @@ -4765,11 +4791,9 @@ type AiTextToSpeechInput = { prompt: string; lang?: string; }; -type AiTextToSpeechOutput = - | Uint8Array - | { - audio: string; - }; +type AiTextToSpeechOutput = Uint8Array | { + audio: string; +}; declare abstract class BaseAiTextToSpeech { inputs: AiTextToSpeechInput; postProcessedOutputs: AiTextToSpeechOutput; @@ -4911,22 +4935,18 @@ type ChatCompletionToolChoiceAllowedTools = { type ChatCompletionToolChoiceOption = "none" | "auto" | "required" | ChatCompletionToolChoiceFunction | ChatCompletionToolChoiceCustom | ChatCompletionToolChoiceAllowedTools; type DeveloperMessage = { role: "developer"; - content: - | string - | Array<{ - type: "text"; - text: string; - }>; + content: string | Array<{ + type: "text"; + text: string; + }>; name?: string; }; type SystemMessage = { role: "system"; - content: - | string - | Array<{ - type: "text"; - text: string; - }>; + content: string | Array<{ + type: "text"; + text: string; + }>; name?: string; }; /** @@ -4979,12 +4999,10 @@ type AssistantMessage = { }; type ToolMessage = { role: "tool"; - content: - | string - | Array<{ - type: "text"; - text: string; - }>; + content: string | Array<{ + type: "text"; + text: string; + }>; tool_call_id: string; }; type FunctionMessage = { @@ -5015,19 +5033,15 @@ type ChatCompletionsStreamOptions = { }; type PredictionContent = { type: "content"; - content: - | string - | Array<{ - type: "text"; - text: string; - }>; + content: string | Array<{ + type: "text"; + text: string; + }>; }; type AudioParams = { - voice: - | string - | { - id: string; - }; + voice: string | { + id: string; + }; format: "wav" | "aac" | "mp3" | "flac" | "opus" | "pcm16"; }; type WebSearchUserLocation = { @@ -5080,12 +5094,9 @@ type ChatCompletionsCommonOptions = { top_p?: number | null; user?: string; web_search_options?: WebSearchOptions; - function_call?: - | "none" - | "auto" - | { - name: string; - }; + function_call?: "none" | "auto" | { + name: string; + }; functions?: Array; }; type PromptTokensDetails = { @@ -5273,25 +5284,7 @@ type ResponseCustomToolCallOutput = { id?: string; }; type ResponseError = { - code: - | "server_error" - | "rate_limit_exceeded" - | "invalid_prompt" - | "vector_store_timeout" - | "invalid_image" - | "invalid_image_format" - | "invalid_base64_image" - | "invalid_image_url" - | "image_too_large" - | "image_too_small" - | "image_parse_error" - | "image_content_policy_violation" - | "invalid_image_mode" - | "image_file_too_large" - | "unsupported_image_media_type" - | "empty_image_file" - | "failed_to_download_image" - | "image_file_not_found"; + code: "server_error" | "rate_limit_exceeded" | "invalid_prompt" | "vector_store_timeout" | "invalid_image" | "invalid_image_format" | "invalid_base64_image" | "invalid_image_url" | "image_too_large" | "image_too_small" | "image_parse_error" | "image_content_policy_violation" | "invalid_image_mode" | "image_file_too_large" | "unsupported_image_media_type" | "empty_image_file" | "failed_to_download_image" | "image_file_not_found"; message: string; }; type ResponseErrorEvent = { @@ -5490,22 +5483,7 @@ type ResponseRefusalDoneEvent = { type: "response.refusal.done"; }; type ResponseStatus = "completed" | "failed" | "in_progress" | "cancelled" | "queued" | "incomplete"; -type ResponseStreamEvent = - | ResponseCompletedEvent - | ResponseCreatedEvent - | ResponseErrorEvent - | ResponseFunctionCallArgumentsDeltaEvent - | ResponseFunctionCallArgumentsDoneEvent - | ResponseFailedEvent - | ResponseIncompleteEvent - | ResponseOutputItemAddedEvent - | ResponseOutputItemDoneEvent - | ResponseReasoningTextDeltaEvent - | ResponseReasoningTextDoneEvent - | ResponseRefusalDeltaEvent - | ResponseRefusalDoneEvent - | ResponseTextDeltaEvent - | ResponseTextDoneEvent; +type ResponseStreamEvent = ResponseCompletedEvent | ResponseCreatedEvent | ResponseErrorEvent | ResponseFunctionCallArgumentsDeltaEvent | ResponseFunctionCallArgumentsDoneEvent | ResponseFailedEvent | ResponseIncompleteEvent | ResponseOutputItemAddedEvent | ResponseOutputItemDoneEvent | ResponseReasoningTextDeltaEvent | ResponseReasoningTextDoneEvent | ResponseRefusalDeltaEvent | ResponseRefusalDoneEvent | ResponseTextDeltaEvent | ResponseTextDoneEvent; type ResponseCompletedEvent = { response: Response; sequence_number: number; @@ -5563,39 +5541,35 @@ type Without = { }; /** Either T or U, but not both (mutually exclusive) */ type XOR = (T & Without) | (U & Without); -type Ai_Cf_Baai_Bge_Base_En_V1_5_Input = - | { - text: string | string[]; - /** - * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. - */ - pooling?: "mean" | "cls"; - } - | { - /** - * Batch of the embeddings requests to run using async-queue - */ - requests: { - text: string | string[]; - /** - * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. - */ - pooling?: "mean" | "cls"; - }[]; - }; -type Ai_Cf_Baai_Bge_Base_En_V1_5_Output = - | { - shape?: number[]; - /** - * Embeddings of the requested text values - */ - data?: number[][]; - /** - * The pooling method used in the embedding process. - */ - pooling?: "mean" | "cls"; - } - | Ai_Cf_Baai_Bge_Base_En_V1_5_AsyncResponse; +type Ai_Cf_Baai_Bge_Base_En_V1_5_Input = { + text: string | string[]; + /** + * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. + */ + pooling?: "mean" | "cls"; +} | { + /** + * Batch of the embeddings requests to run using async-queue + */ + requests: { + text: string | string[]; + /** + * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. + */ + pooling?: "mean" | "cls"; + }[]; +}; +type Ai_Cf_Baai_Bge_Base_En_V1_5_Output = { + shape?: number[]; + /** + * Embeddings of the requested text values + */ + data?: number[][]; + /** + * The pooling method used in the embedding process. + */ + pooling?: "mean" | "cls"; +} | Ai_Cf_Baai_Bge_Base_En_V1_5_AsyncResponse; interface Ai_Cf_Baai_Bge_Base_En_V1_5_AsyncResponse { /** * The async request id that can be used to obtain the results. @@ -5606,14 +5580,12 @@ declare abstract class Base_Ai_Cf_Baai_Bge_Base_En_V1_5 { inputs: Ai_Cf_Baai_Bge_Base_En_V1_5_Input; postProcessedOutputs: Ai_Cf_Baai_Bge_Base_En_V1_5_Output; } -type Ai_Cf_Openai_Whisper_Input = - | string - | { - /** - * An array of integers that represent the audio data constrained to 8-bit unsigned integer values - */ - audio: number[]; - }; +type Ai_Cf_Openai_Whisper_Input = string | { + /** + * An array of integers that represent the audio data constrained to 8-bit unsigned integer values + */ + audio: number[]; +}; interface Ai_Cf_Openai_Whisper_Output { /** * The transcription @@ -5637,48 +5609,44 @@ declare abstract class Base_Ai_Cf_Openai_Whisper { inputs: Ai_Cf_Openai_Whisper_Input; postProcessedOutputs: Ai_Cf_Openai_Whisper_Output; } -type Ai_Cf_Meta_M2M100_1_2B_Input = - | { - /** - * The text to be translated - */ - text: string; - /** - * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified - */ - source_lang?: string; - /** - * The language code to translate the text into (e.g., 'es' for Spanish) - */ - target_lang: string; - } - | { - /** - * Batch of the embeddings requests to run using async-queue - */ - requests: { - /** - * The text to be translated - */ - text: string; - /** - * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified - */ - source_lang?: string; - /** - * The language code to translate the text into (e.g., 'es' for Spanish) - */ - target_lang: string; - }[]; - }; -type Ai_Cf_Meta_M2M100_1_2B_Output = - | { - /** - * The translated text in the target language - */ - translated_text?: string; - } - | Ai_Cf_Meta_M2M100_1_2B_AsyncResponse; +type Ai_Cf_Meta_M2M100_1_2B_Input = { + /** + * The text to be translated + */ + text: string; + /** + * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified + */ + source_lang?: string; + /** + * The language code to translate the text into (e.g., 'es' for Spanish) + */ + target_lang: string; +} | { + /** + * Batch of the embeddings requests to run using async-queue + */ + requests: { + /** + * The text to be translated + */ + text: string; + /** + * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified + */ + source_lang?: string; + /** + * The language code to translate the text into (e.g., 'es' for Spanish) + */ + target_lang: string; + }[]; +}; +type Ai_Cf_Meta_M2M100_1_2B_Output = { + /** + * The translated text in the target language + */ + translated_text?: string; +} | Ai_Cf_Meta_M2M100_1_2B_AsyncResponse; interface Ai_Cf_Meta_M2M100_1_2B_AsyncResponse { /** * The async request id that can be used to obtain the results. @@ -5689,39 +5657,35 @@ declare abstract class Base_Ai_Cf_Meta_M2M100_1_2B { inputs: Ai_Cf_Meta_M2M100_1_2B_Input; postProcessedOutputs: Ai_Cf_Meta_M2M100_1_2B_Output; } -type Ai_Cf_Baai_Bge_Small_En_V1_5_Input = - | { - text: string | string[]; - /** - * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. - */ - pooling?: "mean" | "cls"; - } - | { - /** - * Batch of the embeddings requests to run using async-queue - */ - requests: { - text: string | string[]; - /** - * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. - */ - pooling?: "mean" | "cls"; - }[]; - }; -type Ai_Cf_Baai_Bge_Small_En_V1_5_Output = - | { - shape?: number[]; - /** - * Embeddings of the requested text values - */ - data?: number[][]; - /** - * The pooling method used in the embedding process. - */ - pooling?: "mean" | "cls"; - } - | Ai_Cf_Baai_Bge_Small_En_V1_5_AsyncResponse; +type Ai_Cf_Baai_Bge_Small_En_V1_5_Input = { + text: string | string[]; + /** + * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. + */ + pooling?: "mean" | "cls"; +} | { + /** + * Batch of the embeddings requests to run using async-queue + */ + requests: { + text: string | string[]; + /** + * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. + */ + pooling?: "mean" | "cls"; + }[]; +}; +type Ai_Cf_Baai_Bge_Small_En_V1_5_Output = { + shape?: number[]; + /** + * Embeddings of the requested text values + */ + data?: number[][]; + /** + * The pooling method used in the embedding process. + */ + pooling?: "mean" | "cls"; +} | Ai_Cf_Baai_Bge_Small_En_V1_5_AsyncResponse; interface Ai_Cf_Baai_Bge_Small_En_V1_5_AsyncResponse { /** * The async request id that can be used to obtain the results. @@ -5732,39 +5696,35 @@ declare abstract class Base_Ai_Cf_Baai_Bge_Small_En_V1_5 { inputs: Ai_Cf_Baai_Bge_Small_En_V1_5_Input; postProcessedOutputs: Ai_Cf_Baai_Bge_Small_En_V1_5_Output; } -type Ai_Cf_Baai_Bge_Large_En_V1_5_Input = - | { - text: string | string[]; - /** - * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. - */ - pooling?: "mean" | "cls"; - } - | { - /** - * Batch of the embeddings requests to run using async-queue - */ - requests: { - text: string | string[]; - /** - * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. - */ - pooling?: "mean" | "cls"; - }[]; - }; -type Ai_Cf_Baai_Bge_Large_En_V1_5_Output = - | { - shape?: number[]; - /** - * Embeddings of the requested text values - */ - data?: number[][]; - /** - * The pooling method used in the embedding process. - */ - pooling?: "mean" | "cls"; - } - | Ai_Cf_Baai_Bge_Large_En_V1_5_AsyncResponse; +type Ai_Cf_Baai_Bge_Large_En_V1_5_Input = { + text: string | string[]; + /** + * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. + */ + pooling?: "mean" | "cls"; +} | { + /** + * Batch of the embeddings requests to run using async-queue + */ + requests: { + text: string | string[]; + /** + * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. + */ + pooling?: "mean" | "cls"; + }[]; +}; +type Ai_Cf_Baai_Bge_Large_En_V1_5_Output = { + shape?: number[]; + /** + * Embeddings of the requested text values + */ + data?: number[][]; + /** + * The pooling method used in the embedding process. + */ + pooling?: "mean" | "cls"; +} | Ai_Cf_Baai_Bge_Large_En_V1_5_AsyncResponse; interface Ai_Cf_Baai_Bge_Large_En_V1_5_AsyncResponse { /** * The async request id that can be used to obtain the results. @@ -5775,47 +5735,45 @@ declare abstract class Base_Ai_Cf_Baai_Bge_Large_En_V1_5 { inputs: Ai_Cf_Baai_Bge_Large_En_V1_5_Input; postProcessedOutputs: Ai_Cf_Baai_Bge_Large_En_V1_5_Output; } -type Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input = - | string - | { - /** - * The input text prompt for the model to generate a response. - */ - prompt?: string; - /** - * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. - */ - raw?: boolean; - /** - * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. - */ - top_p?: number; - /** - * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. - */ - top_k?: number; - /** - * Random seed for reproducibility of the generation. - */ - seed?: number; - /** - * Penalty for repeated tokens; higher values discourage repetition. - */ - repetition_penalty?: number; - /** - * Decreases the likelihood of the model repeating the same lines verbatim. - */ - frequency_penalty?: number; - /** - * Increases the likelihood of the model introducing new topics. - */ - presence_penalty?: number; - image: number[] | (string & NonNullable); - /** - * The maximum number of tokens to generate in the response. - */ - max_tokens?: number; - }; +type Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input = string | { + /** + * The input text prompt for the model to generate a response. + */ + prompt?: string; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; + image: number[] | (string & NonNullable); + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; +}; interface Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output { description?: string; } @@ -5823,14 +5781,12 @@ declare abstract class Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M { inputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input; postProcessedOutputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output; } -type Ai_Cf_Openai_Whisper_Tiny_En_Input = - | string - | { - /** - * An array of integers that represent the audio data constrained to 8-bit unsigned integer values - */ - audio: number[]; - }; +type Ai_Cf_Openai_Whisper_Tiny_En_Input = string | { + /** + * An array of integers that represent the audio data constrained to 8-bit unsigned integer values + */ + audio: number[]; +}; interface Ai_Cf_Openai_Whisper_Tiny_En_Output { /** * The transcription @@ -5855,12 +5811,10 @@ declare abstract class Base_Ai_Cf_Openai_Whisper_Tiny_En { postProcessedOutputs: Ai_Cf_Openai_Whisper_Tiny_En_Output; } interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input { - audio: - | string - | { - body?: object; - contentType?: string; - }; + audio: string | { + body?: object; + contentType?: string; + }; /** * Supported tasks are 'translate' or 'transcribe'. */ @@ -5986,15 +5940,12 @@ declare abstract class Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo { inputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input; postProcessedOutputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output; } -type Ai_Cf_Baai_Bge_M3_Input = - | Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts - | Ai_Cf_Baai_Bge_M3_Input_Embedding - | { - /** - * Batch of the embeddings requests to run using async-queue - */ - requests: (Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts_1 | Ai_Cf_Baai_Bge_M3_Input_Embedding_1)[]; - }; +type Ai_Cf_Baai_Bge_M3_Input = Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts | Ai_Cf_Baai_Bge_M3_Input_Embedding | { + /** + * Batch of the embeddings requests to run using async-queue + */ + requests: (Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts_1 | Ai_Cf_Baai_Bge_M3_Input_Embedding_1)[]; +}; interface Ai_Cf_Baai_Bge_M3_Input_QueryAnd_Contexts { /** * A query you wish to perform against the provided contexts. If no query is provided the model with respond with embeddings for contexts @@ -6174,34 +6125,31 @@ interface Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Messages { * The tool call id. If you don't know what to put here you can fall back to 000000001 */ tool_call_id?: string; - content?: - | string - | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }[] - | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }; + content?: string | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }[] | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }; }[]; image?: number[] | (string & NonNullable); functions?: { @@ -6211,93 +6159,90 @@ interface Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Messages { /** * A list of tools available for the assistant to use. */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; /** * If true, the response will be streamed back incrementally. */ @@ -6423,18 +6368,16 @@ interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages { * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). */ role: string; - content: - | string - | { - /** - * Type of the content (text) - */ - type?: string; - /** - * Text content - */ - text?: string; - }[]; + content: string | { + /** + * Type of the content (text) + */ + type?: string; + /** + * Text content + */ + text?: string; + }[]; }[]; functions?: { name: string; @@ -6443,93 +6386,90 @@ interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages { /** * A list of tools available for the assistant to use. */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; response_format?: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_1; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. @@ -6625,45 +6565,42 @@ interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_JSON_Mode_2 { type?: "json_object" | "json_schema"; json_schema?: unknown; } -type Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output = - | { - /** - * The generated text response from the model - */ - response: string; - /** - * Usage statistics for the inference request - */ - usage?: { - /** - * Total number of tokens in input - */ - prompt_tokens?: number; - /** - * Total number of tokens in output - */ - completion_tokens?: number; - /** - * Total number of input and output tokens - */ - total_tokens?: number; - }; - /** - * An array of tool calls requests made during the response generation - */ - tool_calls?: { - /** - * The arguments passed to be passed to the tool call request - */ - arguments?: object; - /** - * The name of the tool to be called - */ - name?: string; - }[]; - } - | string - | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_AsyncResponse; +type Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output = { + /** + * The generated text response from the model + */ + response: string; + /** + * Usage statistics for the inference request + */ + usage?: { + /** + * Total number of tokens in input + */ + prompt_tokens?: number; + /** + * Total number of tokens in output + */ + completion_tokens?: number; + /** + * Total number of input and output tokens + */ + total_tokens?: number; + }; + /** + * An array of tool calls requests made during the response generation + */ + tool_calls?: { + /** + * The arguments passed to be passed to the tool call request + */ + arguments?: object; + /** + * The name of the tool to be called + */ + name?: string; + }[]; +} | string | Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_AsyncResponse; interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_AsyncResponse { /** * The async request id that can be used to obtain the results. @@ -6707,18 +6644,16 @@ interface Ai_Cf_Meta_Llama_Guard_3_8B_Input { }; } interface Ai_Cf_Meta_Llama_Guard_3_8B_Output { - response?: - | string - | { - /** - * Whether the conversation is safe or not. - */ - safe?: boolean; - /** - * A list of what hazard categories predicted for the conversation, if the conversation is deemed unsafe. - */ - categories?: string[]; - }; + response?: string | { + /** + * Whether the conversation is safe or not. + */ + safe?: boolean; + /** + * A list of what hazard categories predicted for the conversation, if the conversation is deemed unsafe. + */ + categories?: string[]; + }; /** * Usage statistics for the inference request */ @@ -6852,93 +6787,90 @@ interface Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Messages { /** * A list of tools available for the assistant to use. */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; response_format?: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_JSON_Mode_1; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. @@ -7089,34 +7021,31 @@ interface Ai_Cf_Qwen_Qwq_32B_Messages { * The tool call id. If you don't know what to put here you can fall back to 000000001 */ tool_call_id?: string; - content?: - | string - | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }[] - | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }; + content?: string | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }[] | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }; }[]; functions?: { name: string; @@ -7125,93 +7054,90 @@ interface Ai_Cf_Qwen_Qwq_32B_Messages { /** * A list of tools available for the assistant to use. */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; /** * JSON schema that should be fufilled for the response. */ @@ -7361,34 +7287,31 @@ interface Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Messages { * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001 */ tool_call_id?: string; - content?: - | string - | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }[] - | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }; + content?: string | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }[] | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }; }[]; functions?: { name: string; @@ -7397,93 +7320,90 @@ interface Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Messages { /** * A list of tools available for the assistant to use. */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; /** * JSON schema that should be fufilled for the response. */ @@ -7629,21 +7549,19 @@ interface Ai_Cf_Google_Gemma_3_12B_It_Messages { * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). */ role?: string; - content?: - | string - | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }[]; + content?: string | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }[]; }[]; functions?: { name: string; @@ -7652,93 +7570,90 @@ interface Ai_Cf_Google_Gemma_3_12B_It_Messages { /** * A list of tools available for the assistant to use. */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; /** * JSON schema that should be fufilled for the response. */ @@ -7893,34 +7808,31 @@ interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages { * The tool call id. If you don't know what to put here you can fall back to 000000001 */ tool_call_id?: string; - content?: - | string - | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }[] - | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }; + content?: string | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }[] | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }; }[]; functions?: { name: string; @@ -7929,93 +7841,90 @@ interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages { /** * A list of tools available for the assistant to use. */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode; /** * JSON schema that should be fufilled for the response. @@ -8129,34 +8038,31 @@ interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages_Inner { * The tool call id. If you don't know what to put here you can fall back to 000000001 */ tool_call_id?: string; - content?: - | string - | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }[] - | { - /** - * Type of the content provided - */ - type?: string; - text?: string; - image_url?: { - /** - * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted - */ - url?: string; - }; - }; + content?: string | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }[] | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }; }[]; functions?: { name: string; @@ -8165,93 +8071,90 @@ interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages_Inner { /** * A list of tools available for the assistant to use. */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode; /** * JSON schema that should be fufilled for the response. @@ -8416,18 +8319,16 @@ interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages { * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). */ role: string; - content: - | string - | { - /** - * Type of the content (text) - */ - type?: string; - /** - * Text content - */ - text?: string; - }[]; + content: string | { + /** + * Type of the content (text) + */ + type?: string; + /** + * Text content + */ + text?: string; + }[]; }[]; functions?: { name: string; @@ -8436,93 +8337,90 @@ interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages { /** * A list of tools available for the assistant to use. */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_1; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. @@ -8636,18 +8534,16 @@ interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages_1 { * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). */ role: string; - content: - | string - | { - /** - * Type of the content (text) - */ - type?: string; - /** - * Text content - */ - text?: string; - }[]; + content: string | { + /** + * Type of the content (text) + */ + type?: string; + /** + * Text content + */ + text?: string; + }[]; }[]; functions?: { name: string; @@ -8656,93 +8552,90 @@ interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages_1 { /** * A list of tools available for the assistant to use. */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; response_format?: Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_JSON_Mode_3; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. @@ -9164,30 +9057,28 @@ declare abstract class Base_Ai_Cf_Qwen_Qwen3_Embedding_0_6B { inputs: Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Input; postProcessedOutputs: Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Output; } -type Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Input = - | { - /** - * readable stream with audio data and content-type specified for that data - */ - audio: { - body: object; - contentType: string; - }; - /** - * type of data PCM data that's sent to the inference server as raw array - */ - dtype?: "uint8" | "float32" | "float64"; - } - | { - /** - * base64 encoded audio data - */ - audio: string; - /** - * type of data PCM data that's sent to the inference server as raw array - */ - dtype?: "uint8" | "float32" | "float64"; - }; +type Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Input = { + /** + * readable stream with audio data and content-type specified for that data + */ + audio: { + body: object; + contentType: string; + }; + /** + * type of data PCM data that's sent to the inference server as raw array + */ + dtype?: "uint8" | "float32" | "float64"; +} | { + /** + * base64 encoded audio data + */ + audio: string; + /** + * type of data PCM data that's sent to the inference server as raw array + */ + dtype?: "uint8" | "float32" | "float64"; +}; interface Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Output { /** * if true, end-of-turn was detected @@ -9330,41 +9221,7 @@ interface Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Input { /** * Target langauge to translate to */ - target_language: - | "asm_Beng" - | "awa_Deva" - | "ben_Beng" - | "bho_Deva" - | "brx_Deva" - | "doi_Deva" - | "eng_Latn" - | "gom_Deva" - | "gon_Deva" - | "guj_Gujr" - | "hin_Deva" - | "hne_Deva" - | "kan_Knda" - | "kas_Arab" - | "kas_Deva" - | "kha_Latn" - | "lus_Latn" - | "mag_Deva" - | "mai_Deva" - | "mal_Mlym" - | "mar_Deva" - | "mni_Beng" - | "mni_Mtei" - | "npi_Deva" - | "ory_Orya" - | "pan_Guru" - | "san_Deva" - | "sat_Olck" - | "snd_Arab" - | "snd_Deva" - | "tam_Taml" - | "tel_Telu" - | "urd_Arab" - | "unr_Deva"; + target_language: "asm_Beng" | "awa_Deva" | "ben_Beng" | "bho_Deva" | "brx_Deva" | "doi_Deva" | "eng_Latn" | "gom_Deva" | "gon_Deva" | "guj_Gujr" | "hin_Deva" | "hne_Deva" | "kan_Knda" | "kas_Arab" | "kas_Deva" | "kha_Latn" | "lus_Latn" | "mag_Deva" | "mai_Deva" | "mal_Mlym" | "mar_Deva" | "mni_Beng" | "mni_Mtei" | "npi_Deva" | "ory_Orya" | "pan_Guru" | "san_Deva" | "sat_Olck" | "snd_Arab" | "snd_Deva" | "tam_Taml" | "tel_Telu" | "urd_Arab" | "unr_Deva"; } interface Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Output { /** @@ -9441,18 +9298,16 @@ interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages { * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). */ role: string; - content: - | string - | { - /** - * Type of the content (text) - */ - type?: string; - /** - * Text content - */ - text?: string; - }[]; + content: string | { + /** + * Type of the content (text) + */ + type?: string; + /** + * Text content + */ + text?: string; + }[]; }[]; functions?: { name: string; @@ -9461,93 +9316,90 @@ interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages { /** * A list of tools available for the assistant to use. */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_1; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. @@ -9661,18 +9513,16 @@ interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages_1 { * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). */ role: string; - content: - | string - | { - /** - * Type of the content (text) - */ - type?: string; - /** - * Text content - */ - text?: string; - }[]; + content: string | { + /** + * Type of the content (text) + */ + type?: string; + /** + * Text content + */ + text?: string; + }[]; }[]; functions?: { name: string; @@ -9681,93 +9531,90 @@ interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages_1 { /** * A list of tools available for the assistant to use. */ - tools?: ( - | { - /** - * The name of the tool. More descriptive the better. - */ - name: string; - /** - * A brief description of what the tool does. - */ - description: string; - /** - * Schema defining the parameters accepted by the tool. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - } - | { - /** - * Specifies the type of tool (e.g., 'function'). - */ - type: string; - /** - * Details of the function tool. - */ - function: { - /** - * The name of the function. - */ - name: string; - /** - * A brief description of what the function does. - */ - description: string; - /** - * Schema defining the parameters accepted by the function. - */ - parameters: { - /** - * The type of the parameters object (usually 'object'). - */ - type: string; - /** - * List of required parameter names. - */ - required?: string[]; - /** - * Definitions of each parameter. - */ - properties: { - [k: string]: { - /** - * The data type of the parameter. - */ - type: string; - /** - * A description of the expected parameter. - */ - description: string; - }; - }; - }; - }; - } - )[]; + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; response_format?: Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_JSON_Mode_3; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. @@ -10006,7 +9853,10 @@ interface Ai_Cf_Pfnet_Plamo_Embedding_1B_Output { * @minItems 2 * @maxItems 2 */ - shape: [number, number]; + shape: [ + number, + number + ]; } declare abstract class Base_Ai_Cf_Pfnet_Plamo_Embedding_1B { inputs: Ai_Cf_Pfnet_Plamo_Embedding_1B_Input; @@ -10104,47 +9954,7 @@ interface Ai_Cf_Deepgram_Aura_2_En_Input { /** * Speaker used to produce the audio. */ - speaker?: - | "amalthea" - | "andromeda" - | "apollo" - | "arcas" - | "aries" - | "asteria" - | "athena" - | "atlas" - | "aurora" - | "callista" - | "cora" - | "cordelia" - | "delia" - | "draco" - | "electra" - | "harmonia" - | "helena" - | "hera" - | "hermes" - | "hyperion" - | "iris" - | "janus" - | "juno" - | "jupiter" - | "luna" - | "mars" - | "minerva" - | "neptune" - | "odysseus" - | "ophelia" - | "orion" - | "orpheus" - | "pandora" - | "phoebe" - | "pluto" - | "saturn" - | "thalia" - | "theia" - | "vesta" - | "zeus"; + speaker?: "amalthea" | "andromeda" | "apollo" | "arcas" | "aries" | "asteria" | "athena" | "atlas" | "aurora" | "callista" | "cora" | "cordelia" | "delia" | "draco" | "electra" | "harmonia" | "helena" | "hera" | "hermes" | "hyperion" | "iris" | "janus" | "juno" | "jupiter" | "luna" | "mars" | "minerva" | "neptune" | "odysseus" | "ophelia" | "orion" | "orpheus" | "pandora" | "phoebe" | "pluto" | "saturn" | "thalia" | "theia" | "vesta" | "zeus"; /** * Encoding of the output audio. */ @@ -10422,8 +10232,10 @@ type AiModelsSearchObject = { }; type ChatCompletionsBase = ChatCompletionsMessagesInput; type ChatCompletionsInput = ChatCompletionsMessagesInput; -interface InferenceUpstreamError extends Error {} -interface AiInternalError extends Error {} +interface InferenceUpstreamError extends Error { +} +interface AiInternalError extends Error { +} type AiModelListType = Record; type AiAsyncBatchResponse = { request_id: string; @@ -10445,41 +10257,25 @@ declare abstract class Ai { */ autorag(autoragId: string): AutoRAG; // Batch request - run( - model: Name, - inputs: { - requests: AiModelList[Name]["inputs"][]; - }, - options: AiOptions & { - queueRequest: true; - }, - ): Promise; + run(model: Name, inputs: { + requests: AiModelList[Name]['inputs'][]; + }, options: AiOptions & { + queueRequest: true; + }): Promise; // Raw response - run( - model: Name, - inputs: AiModelList[Name]["inputs"], - options: AiOptions & { - returnRawResponse: true; - }, - ): Promise; + run(model: Name, inputs: AiModelList[Name]['inputs'], options: AiOptions & { + returnRawResponse: true; + }): Promise; // WebSocket - run( - model: Name, - inputs: AiModelList[Name]["inputs"], - options: AiOptions & { - websocket: true; - }, - ): Promise; + run(model: Name, inputs: AiModelList[Name]['inputs'], options: AiOptions & { + websocket: true; + }): Promise; // Streaming - run( - model: Name, - inputs: AiModelList[Name]["inputs"] & { - stream: true; - }, - options?: AiOptions, - ): Promise; + run(model: Name, inputs: AiModelList[Name]['inputs'] & { + stream: true; + }, options?: AiOptions): Promise; // Normal (default) - known model - run(model: Name, inputs: AiModelList[Name]["inputs"], options?: AiOptions): Promise; + run(model: Name, inputs: AiModelList[Name]['inputs'], options?: AiOptions): Promise; // Unknown model (fallback). // // The `Exclude<..., keyof AiModelList>` constraint forces TypeScript to @@ -10497,7 +10293,7 @@ declare abstract class Ai { type GatewayRetries = { maxAttempts?: 1 | 2 | 3 | 4 | 5; retryDelayMs?: number; - backoff?: "constant" | "linear" | "exponential"; + backoff?: 'constant' | 'linear' | 'exponential'; }; type GatewayOptions = { id: string; @@ -10510,7 +10306,7 @@ type GatewayOptions = { requestTimeoutMs?: number; retries?: GatewayRetries; }; -type UniversalGatewayOptions = Exclude & { +type UniversalGatewayOptions = Exclude & { /** ** @deprecated */ @@ -10548,29 +10344,26 @@ type AiGatewayLog = { response_head_complete: boolean; created_at: Date; }; -type AIGatewayProviders = "workers-ai" | "anthropic" | "aws-bedrock" | "azure-openai" | "google-vertex-ai" | "huggingface" | "openai" | "perplexity-ai" | "replicate" | "groq" | "cohere" | "google-ai-studio" | "mistral" | "grok" | "openrouter" | "deepseek" | "cerebras" | "cartesia" | "elevenlabs" | "adobe-firefly"; +type AIGatewayProviders = 'workers-ai' | 'anthropic' | 'aws-bedrock' | 'azure-openai' | 'google-vertex-ai' | 'huggingface' | 'openai' | 'perplexity-ai' | 'replicate' | 'groq' | 'cohere' | 'google-ai-studio' | 'mistral' | 'grok' | 'openrouter' | 'deepseek' | 'cerebras' | 'cartesia' | 'elevenlabs' | 'adobe-firefly'; type AIGatewayHeaders = { - "cf-aig-metadata": Record | string; - "cf-aig-custom-cost": - | { - per_token_in?: number; - per_token_out?: number; - } - | { - total_cost?: number; - } - | string; - "cf-aig-cache-ttl": number | string; - "cf-aig-skip-cache": boolean | string; - "cf-aig-cache-key": string; - "cf-aig-event-id": string; - "cf-aig-request-timeout": number | string; - "cf-aig-max-attempts": number | string; - "cf-aig-retry-delay": number | string; - "cf-aig-backoff": string; - "cf-aig-collect-log": boolean | string; + 'cf-aig-metadata': Record | string; + 'cf-aig-custom-cost': { + per_token_in?: number; + per_token_out?: number; + } | { + total_cost?: number; + } | string; + 'cf-aig-cache-ttl': number | string; + 'cf-aig-skip-cache': boolean | string; + 'cf-aig-cache-key': string; + 'cf-aig-event-id': string; + 'cf-aig-request-timeout': number | string; + 'cf-aig-max-attempts': number | string; + 'cf-aig-retry-delay': number | string; + 'cf-aig-backoff': string; + 'cf-aig-collect-log': boolean | string; Authorization: string; - "Content-Type": string; + 'Content-Type': string; [key: string]: string | number | boolean | object; }; type AIGatewayUniversalRequest = { @@ -10579,19 +10372,18 @@ type AIGatewayUniversalRequest = { headers: Partial; query: unknown; }; -interface AiGatewayInternalError extends Error {} -interface AiGatewayLogNotFound extends Error {} +interface AiGatewayInternalError extends Error { +} +interface AiGatewayLogNotFound extends Error { +} declare abstract class AiGateway { patchLog(logId: string, data: AiGatewayPatchLog): Promise; getLog(logId: string): Promise; - run( - data: AIGatewayUniversalRequest | AIGatewayUniversalRequest[], - options?: { - gateway?: UniversalGatewayOptions; - extraHeaders?: object; - signal?: AbortSignal; - }, - ): Promise; + run(data: AIGatewayUniversalRequest | AIGatewayUniversalRequest[], options?: { + gateway?: UniversalGatewayOptions; + extraHeaders?: object; + signal?: AbortSignal; + }): Promise; getUrl(provider?: AIGatewayProviders | string): Promise; // eslint-disable-line } // Copyright (c) 2022-2025 Cloudflare, Inc. @@ -10646,7 +10438,7 @@ interface ArtifactsCreateRepoResult { /** Paginated list of repositories. */ interface ArtifactsRepoListResult { /** Repositories in this page (without the `remote` field). */ - repos: Omit[]; + repos: Omit[]; /** Total number of repositories in the namespace. */ total: number; /** Cursor for the next page, if there are more results. */ @@ -10659,7 +10451,7 @@ interface ArtifactsCreateTokenResult { /** Plaintext token (only returned at creation time). */ plaintext: string; /** Token scope: "read" or "write". */ - scope: "read" | "write"; + scope: 'read' | 'write'; /** ISO 8601 token expiry timestamp. */ expiresAt: string; } @@ -10668,9 +10460,9 @@ interface ArtifactsTokenInfo { /** Unique token ID. */ id: string; /** Token scope: "read" or "write". */ - scope: "read" | "write"; + scope: 'read' | 'write'; /** Token state: "active", "expired", or "revoked". */ - state: "active" | "expired" | "revoked"; + state: 'active' | 'expired' | 'revoked'; /** ISO 8601 creation timestamp. */ createdAt: string; /** ISO 8601 expiry timestamp. */ @@ -10695,7 +10487,7 @@ interface ArtifactsRepo extends ArtifactsRepoInfo { * @param ttl Time-to-live in seconds (default 86400, min 60, max 31536000). * @throws {ArtifactsError} with code `INVALID_TTL` if ttl is out of range. */ - createToken(scope?: "write" | "read", ttl?: number): Promise; + createToken(scope?: 'write' | 'read', ttl?: number): Promise; /** List tokens for this repo (metadata only, no plaintext). */ listTokens(): Promise; /** @@ -10714,14 +10506,11 @@ interface ArtifactsRepo extends ArtifactsRepoInfo { * @throws {ArtifactsError} with code `ALREADY_EXISTS` if the target repo already exists. * @throws {ArtifactsError} with code `FORK_IN_PROGRESS` if a fork is already running. */ - fork( - name: string, - opts?: { - description?: string; - readOnly?: boolean; - defaultBranchOnly?: boolean; - }, - ): Promise; + fork(name: string, opts?: { + description?: string; + readOnly?: boolean; + defaultBranchOnly?: boolean; + }): Promise; } // ── Error types ────────────────────────────────────────────────────────────── /** @@ -10729,7 +10518,7 @@ interface ArtifactsRepo extends ArtifactsRepoInfo { * * Each code maps to a numeric code available on `ArtifactsError.numericCode`. */ -type ArtifactsErrorCode = "ALREADY_EXISTS" | "NOT_FOUND" | "IMPORT_IN_PROGRESS" | "FORK_IN_PROGRESS" | "INVALID_INPUT" | "INVALID_REPO_NAME" | "INVALID_TTL" | "INVALID_URL" | "REMOTE_AUTH_REQUIRED" | "UPSTREAM_UNAVAILABLE" | "MEMORY_LIMIT" | "INTERNAL_ERROR"; +type ArtifactsErrorCode = 'ALREADY_EXISTS' | 'NOT_FOUND' | 'IMPORT_IN_PROGRESS' | 'FORK_IN_PROGRESS' | 'INVALID_INPUT' | 'INVALID_REPO_NAME' | 'INVALID_TTL' | 'INVALID_URL' | 'REMOTE_AUTH_REQUIRED' | 'UPSTREAM_UNAVAILABLE' | 'MEMORY_LIMIT' | 'INTERNAL_ERROR'; /** * Error thrown by Artifacts binding operations. * @@ -10738,7 +10527,7 @@ type ArtifactsErrorCode = "ALREADY_EXISTS" | "NOT_FOUND" | "IMPORT_IN_PROGRESS" * the REST API `errors[].code` values. */ interface ArtifactsError extends Error { - readonly name: "ArtifactsError"; + readonly name: 'ArtifactsError'; /** String error code for programmatic matching. */ readonly code: ArtifactsErrorCode; /** Numeric error code matching the REST API. */ @@ -10759,14 +10548,11 @@ interface Artifacts { * @throws {ArtifactsError} with code `INVALID_REPO_NAME` if name is invalid. * @throws {ArtifactsError} with code `ALREADY_EXISTS` if the repo already exists. */ - create( - name: string, - opts?: { - readOnly?: boolean; - description?: string; - setDefaultBranch?: string; - }, - ): Promise; + create(name: string, opts?: { + readOnly?: boolean; + description?: string; + setDefaultBranch?: string; + }): Promise; /** * Get a handle to an existing repository. * @param name Repository name. @@ -10807,7 +10593,10 @@ interface Artifacts { * List repositories with cursor-based pagination. * @param opts Optional: limit (1–200, default 50), cursor for next page. */ - list(opts?: { limit?: number; cursor?: string }): Promise; + list(opts?: { + limit?: number; + cursor?: string; + }): Promise; /** * Delete a repository and all associated tokens. * @param name Repository name. @@ -10820,29 +10609,33 @@ interface Artifacts { * @deprecated Use the standalone AI Search Workers binding instead. * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ -interface AutoRAGInternalError extends Error {} +interface AutoRAGInternalError extends Error { +} /** * @deprecated Use the standalone AI Search Workers binding instead. * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ -interface AutoRAGNotFoundError extends Error {} +interface AutoRAGNotFoundError extends Error { +} /** * @deprecated Use the standalone AI Search Workers binding instead. * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ -interface AutoRAGUnauthorizedError extends Error {} +interface AutoRAGUnauthorizedError extends Error { +} /** * @deprecated Use the standalone AI Search Workers binding instead. * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ -interface AutoRAGNameNotSetError extends Error {} +interface AutoRAGNameNotSetError extends Error { +} type ComparisonFilter = { key: string; - type: "eq" | "ne" | "gt" | "gte" | "lt" | "lte"; + type: 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte'; value: string | number | boolean; }; type CompoundFilter = { - type: "and" | "or"; + type: 'and' | 'or'; filters: ComparisonFilter[]; }; /** @@ -10875,7 +10668,7 @@ type AutoRagAiSearchRequest = AutoRagSearchRequest & { * @deprecated Use the standalone AI Search Workers binding instead. * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ -type AutoRagAiSearchRequestStreaming = Omit & { +type AutoRagAiSearchRequestStreaming = Omit & { stream: true; }; /** @@ -10883,7 +10676,7 @@ type AutoRagAiSearchRequestStreaming = Omit & * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ type AutoRagSearchResponse = { - object: "vector_store.search_results.page"; + object: 'vector_store.search_results.page'; search_query: string; data: { file_id: string; @@ -10891,7 +10684,7 @@ type AutoRagSearchResponse = { score: number; attributes: Record; content: { - type: "text"; + type: 'text'; text: string; }[]; }[]; @@ -10949,8 +10742,8 @@ declare abstract class AutoRAG { */ aiSearch(params: AutoRagAiSearchRequest): Promise; } -type BrowserRunLifecycleEvent = "load" | "domcontentloaded" | "networkidle0" | "networkidle2"; -type BrowserRunResourceType = "document" | "stylesheet" | "image" | "media" | "font" | "script" | "texttrack" | "xhr" | "fetch" | "prefetch" | "eventsource" | "websocket" | "manifest" | "signedexchange" | "ping" | "cspviolationreport" | "preflight" | "other"; +type BrowserRunLifecycleEvent = 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'; +type BrowserRunResourceType = 'document' | 'stylesheet' | 'image' | 'media' | 'font' | 'script' | 'texttrack' | 'xhr' | 'fetch' | 'prefetch' | 'eventsource' | 'websocket' | 'manifest' | 'signedexchange' | 'ping' | 'cspviolationreport' | 'preflight' | 'other'; /** Options fields shared by all quick actions. */ interface BrowserRunBaseOptions { /** Adds `