From 3a3f96bba650638863965dbbe634aca060e27937 Mon Sep 17 00:00:00 2001 From: Devin Zuczek Date: Tue, 30 Jun 2026 18:52:37 -0400 Subject: [PATCH] avatar endpoints --- apps/accounts/src/accounts-db.ts | 3 +- apps/api/src/api.app.ts | 45 +- apps/api/src/test/integration/api.test.ts | 20 +- apps/auth/migrations/0002_avatar.sql | 5 + apps/auth/src/accounts-db.ts | 3 +- apps/econ/src/avatar-db.ts | 53 + apps/econ/src/context.ts | 3 +- apps/econ/src/econ.app.ts | 40 +- apps/econ/src/test/integration/api.test.ts | 87 +- apps/econ/worker-configuration.d.ts | 3477 +++++++++++++++++--- apps/econ/wrangler.jsonc | 10 + 11 files changed, 3213 insertions(+), 533 deletions(-) create mode 100644 apps/auth/migrations/0002_avatar.sql create mode 100644 apps/econ/src/avatar-db.ts diff --git a/apps/accounts/src/accounts-db.ts b/apps/accounts/src/accounts-db.ts index 631999d..523538d 100644 --- a/apps/accounts/src/accounts-db.ts +++ b/apps/accounts/src/accounts-db.ts @@ -10,10 +10,11 @@ * and keep these helpers in sync. */ -/** Schema DDL (mirror of migrations/0001_accounts.sql, sans the seed INSERTs). */ +/** Schema DDL (mirror of migrations 0001_accounts + 0002_avatar, sans seed INSERTs). */ export const SCHEMA_DDL: string[] = [ `CREATE TABLE IF NOT EXISTS accounts ( data TEXT NOT NULL, + avatar TEXT, account_id INTEGER GENERATED ALWAYS AS (json_extract(data, '$.AccountId')) VIRTUAL, username_lower TEXT GENERATED ALWAYS AS (lower(json_extract(data, '$.Username'))) VIRTUAL )`, diff --git a/apps/api/src/api.app.ts b/apps/api/src/api.app.ts index 3fd57f0..8636da4 100644 --- a/apps/api/src/api.app.ts +++ b/apps/api/src/api.app.ts @@ -4,12 +4,10 @@ import { useWorkersLogger } from 'workers-tagged-logger' import { withNotFound, withOnError } from '@repo/hono-helpers' import apiConfigV2 from '../static/api-config-v2.json' -import defaultAvatar from '../static/default-avatar.json' import gameConfigsV1All from '../static/gameconfigs-v1-all.json' import storefrontGiftDrop2 from '../static/storefronts-v3-giftdropstore-2.json' import storefrontGiftDrop3 from '../static/storefronts-v3-giftdropstore-3.json' import storefrontGiftDrop300 from '../static/storefronts-v3-giftdropstore-300.json' -import { DEFAULT_AVATAR_ITEMS } from './default-avatar-items' import { defaultSettings } from './default-settings' import { validateAndGetAccountId } from './jwt' import { getRoomById, getRoomByName, getRoomsByCreator, getRoomsByIds } from './rooms-db' @@ -229,45 +227,10 @@ const app = new Hono({ strict: false }) return c.json([]) }) - // ---- Avatar --------------------------------------------------------------- - .get('/api/avatar/v4/items', async (c) => { - const id = await authedId(c) - if (id === null) return unauthorized(c) - // Owned items would be concatenated here; none without a DB binding. - return c.json(DEFAULT_AVATAR_ITEMS) - }) - .get('/api/avatar/v2', async (c) => { - const id = await authedId(c) - if (id === null) return unauthorized(c) - // TODO: load/create PlayerAvatar for `id`. Must return a populated outfit — - // the client NREs on an empty OutfitSelections — so serve a valid default. - return c.json(defaultAvatar) - }) - .post('/api/avatar/v2/set', async (c) => { - const id = await authedId(c) - if (id === null) return unauthorized(c) - const update = await c.req.json>().catch(() => null) - if (update === null) return c.body(null, 400) - // TODO: persist; echo the accepted avatar back. Fall back to the valid - // default avatar fields when the client omits them. - return c.json({ - OwnerAccountId: id, - OutfitSelections: update.OutfitSelections ?? defaultAvatar.OutfitSelections, - FaceFeatures: update.FaceFeatures ?? defaultAvatar.FaceFeatures, - SkinColor: update.SkinColor ?? defaultAvatar.SkinColor, - HairColor: update.HairColor ?? defaultAvatar.HairColor, - }) - }) - .get('/api/avatar/v3/saved', async (c) => { - const id = await authedId(c) - if (id === null) return unauthorized(c) - return c.json([]) // TODO: query SavedOutfits - }) - .get('/api/avatar/v2/gifts', async (c) => { - const id = await authedId(c) - if (id === null) return unauthorized(c) - return c.json([]) // TODO: query pending ReceivedGifts - }) + // ---- Avatar gifts --------------------------------------------------------- + // The avatar read endpoints (`v4/items`, `v2`, `v2/set`, `v3/saved`, `v2/gifts`) + // live in the `econ` worker, which the client calls on the econ host — not here. + // Only the gift generate/consume actions remain on this worker. .post('/api/avatar/v2/gifts/generate', async (c) => { const id = await authedId(c) if (id === null) return unauthorized(c) diff --git a/apps/api/src/test/integration/api.test.ts b/apps/api/src/test/integration/api.test.ts index ed0fd9b..aa19cc8 100644 --- a/apps/api/src/test/integration/api.test.ts +++ b/apps/api/src/test/integration/api.test.ts @@ -4,7 +4,6 @@ import { beforeAll, describe, expect, test } from 'vitest' import '../../api.app' -import { DEFAULT_AVATAR_ITEMS } from '../../default-avatar-items' import type { Env } from '../../context' @@ -246,26 +245,17 @@ describe('public endpoints', () => { describe('auth-gated endpoints', () => { test('401 without a bearer token', async () => { - const res = await exports.default.fetch(`${ORIGIN}/api/avatar/v4/items`) + const res = await exports.default.fetch(`${ORIGIN}/api/settings/v2`) expect(res.status).toBe(401) }) test('401 with a garbage token', async () => { - const res = await exports.default.fetch(`${ORIGIN}/api/avatar/v4/items`, { + const res = await exports.default.fetch(`${ORIGIN}/api/settings/v2`, { headers: { Authorization: 'Bearer not-a-real-token' }, }) expect(res.status).toBe(401) }) - test('GET /api/avatar/v4/items returns default items with a valid token', async () => { - const res = await exports.default.fetch(`${ORIGIN}/api/avatar/v4/items`, { - headers: await bearer(), - }) - expect(res.status).toBe(200) - const items = (await res.json()) as unknown[] - expect(items).toHaveLength(DEFAULT_AVATAR_ITEMS.length) - }) - test('GET /api/settings/v2 returns the default settings for the account', async () => { const res = await exports.default.fetch(`${ORIGIN}/api/settings/v2`, { headers: await bearer(), @@ -276,12 +266,6 @@ describe('auth-gated endpoints', () => { for (const s of settings) expect(s.PlayerId).toBe(42) }) - test('GET /api/avatar/v2 returns a default avatar', async () => { - const res = await exports.default.fetch(`${ORIGIN}/api/avatar/v2`, { headers: await bearer() }) - const body = (await res.json()) as { OutfitSelections: string } - expect(body.OutfitSelections.length).toBeGreaterThan(0) - }) - test('GET /api/checklist/v1/current 401s without a token, returns [] with one', async () => { const anon = await exports.default.fetch(`${ORIGIN}/api/checklist/v1/current`) expect(anon.status).toBe(401) diff --git a/apps/auth/migrations/0002_avatar.sql b/apps/auth/migrations/0002_avatar.sql new file mode 100644 index 0000000..458c288 --- /dev/null +++ b/apps/auth/migrations/0002_avatar.sql @@ -0,0 +1,5 @@ +-- Store the player's avatar (set via the econ worker's /api/avatar/v2/set). It's +-- an opaque JSON payload that isn't queried, so a single nullable TEXT column on +-- the account row suffices. Kept in sync with SCHEMA_DDL in src/accounts-db.ts. + +ALTER TABLE accounts ADD COLUMN avatar TEXT; diff --git a/apps/auth/src/accounts-db.ts b/apps/auth/src/accounts-db.ts index b92d3d2..9f6f050 100644 --- a/apps/auth/src/accounts-db.ts +++ b/apps/auth/src/accounts-db.ts @@ -10,10 +10,11 @@ * and keep these helpers in sync. */ -/** Schema DDL (mirror of migrations/0001_accounts.sql, sans the seed INSERTs). */ +/** Schema DDL (mirror of migrations 0001_accounts + 0002_avatar, sans seed INSERTs). */ export const SCHEMA_DDL: string[] = [ `CREATE TABLE IF NOT EXISTS accounts ( data TEXT NOT NULL, + avatar TEXT, account_id INTEGER GENERATED ALWAYS AS (json_extract(data, '$.AccountId')) VIRTUAL, username_lower TEXT GENERATED ALWAYS AS (lower(json_extract(data, '$.Username'))) VIRTUAL )`, diff --git a/apps/econ/src/avatar-db.ts b/apps/econ/src/avatar-db.ts new file mode 100644 index 0000000..656f64f --- /dev/null +++ b/apps/econ/src/avatar-db.ts @@ -0,0 +1,53 @@ +/** + * Avatar storage on the shared `recflare` accounts table. The avatar is a single + * JSON payload the client sends/consumes and never queries on, so it lives in a + * dedicated nullable `avatar` TEXT column on the player's account row (added by + * the auth worker's migration 0002_avatar). + * + * The `auth` worker owns the accounts schema/migrations; econ only reads/writes + * the avatar column. SCHEMA_DDL mirrors the table so tests can build it without + * depending on the auth worker — keep it in sync with auth's accounts-db.ts. + */ + +/** Schema DDL for tests — the accounts table including the avatar column. */ +export const SCHEMA_DDL: string[] = [ + `CREATE TABLE IF NOT EXISTS accounts ( + data TEXT NOT NULL, + avatar TEXT, + account_id INTEGER GENERATED ALWAYS AS (json_extract(data, '$.AccountId')) VIRTUAL, + username_lower TEXT GENERATED ALWAYS AS (lower(json_extract(data, '$.Username'))) VIRTUAL + )`, + `CREATE UNIQUE INDEX IF NOT EXISTS idx_accounts_account_id ON accounts (account_id)`, +] + +/** The stored avatar payload — opaque JSON the client sets and reads back. */ +export type Avatar = Record + +interface AvatarRow { + avatar: string | null +} + +/** Read the player's stored avatar, or null when they have none yet. */ +export async function getAvatar(db: D1Database, accountId: number): Promise { + const row = await db + .prepare('SELECT avatar FROM accounts WHERE account_id = ?1') + .bind(accountId) + .first() + return row?.avatar ? (JSON.parse(row.avatar) as Avatar) : null +} + +/** + * Persist the player's avatar onto their account row. Returns false when no + * account row exists for the id (nothing was updated). + */ +export async function setAvatar( + db: D1Database, + accountId: number, + avatar: Avatar +): Promise { + const { meta } = await db + .prepare('UPDATE accounts SET avatar = ?2 WHERE account_id = ?1') + .bind(accountId, JSON.stringify(avatar)) + .run() + return meta.changes > 0 +} diff --git a/apps/econ/src/context.ts b/apps/econ/src/context.ts index 329ac40..902068d 100644 --- a/apps/econ/src/context.ts +++ b/apps/econ/src/context.ts @@ -2,7 +2,8 @@ import type { HonoApp } from '@repo/hono-helpers' import type { SharedHonoEnv, SharedHonoVariables } from '@repo/hono-helpers/src/types' export type Env = SharedHonoEnv & { - // add additional Bindings here + /** Shared `recflare` D1 (accounts table) — stores the player's avatar. */ + DB: D1Database } /** Variables can be extended */ diff --git a/apps/econ/src/econ.app.ts b/apps/econ/src/econ.app.ts index 19e96bf..5bc17c3 100644 --- a/apps/econ/src/econ.app.ts +++ b/apps/econ/src/econ.app.ts @@ -8,6 +8,7 @@ import defaultAvatarItems from '../static/default-avatar-items.json' import myProgress from '../static/my-progress.json' import storefrontGiftDrop3 from '../static/storefronts-v3-giftdropstore-3.json' import weeklyChallenge from '../static/weekly-challenge.json' +import { getAvatar, setAvatar } from './avatar-db' import { validateAndGetAccountId } from './jwt' import type { Context } from 'hono' @@ -72,9 +73,8 @@ const app = new Hono() // Default-unlocked avatar items, served from the bundled static JSON. .get('/api/avatar/v1/defaultunlocked', (c) => c.json(defaultAvatarItems)) - // Default base avatar items. Reads the same source file as defaultunlocked, - // so it returns the identical catalog. - .get('/api/avatar/v1/defaultbaseavataritems', (c) => c.json(defaultAvatarItems)) + // Default base avatar items — empty stub for now. No auth. + .get('/api/avatar/v1/defaultbaseavataritems', (c) => c.json([])) // The player's avatar items — owned items concatenated with the default // catalog. No DB binding yet, so owned is empty and this is just the catalog. @@ -85,25 +85,41 @@ const app = new Hono() return c.json(defaultAvatarItems) }) - // The player's owned custom avatar items. No auth; returns `{ items: [] }`. - // The client downloads these when custom-item creation is + // The player's owned custom avatar items. [Authorize]; paginated. Empty stub for + // now (no DB binding). The client downloads these when custom-item creation is // allowed; a 404 here surfaces as "Failed to download unlocked avatar items". - .get('/econ/customAvatarItems/v1/owned', (c) => c.json({ items: [] })) + .get('/econ/customAvatarItems/v1/owned', async (c) => { + const id = await authedId(c) + if (id === null) return unauthorized(c) + return c.json({ Results: [], TotalResults: 0 }) + }) // The player's objectives progress. Serves a static JSON file verbatim with // no auth — same default for everyone until there's a DB binding to track // per-player progress. .get('/api/objectives/v1/myprogress', (c) => c.json(myProgress)) - // The player's avatar. No DB binding yet, so it always returns the default - // for a player with no PlayerAvatar row. + // The player's avatar, stored as a JSON blob on their account row. Falls back + // to the default outfit when they haven't saved one — the client's parser NREs + // on an empty OutfitSelections (real RecNet never returns one). .get('/api/avatar/v2', async (c) => { const id = await authedId(c) if (id === null) return unauthorized(c) - // TODO: load/create the PlayerAvatar for `id` once a DB binding exists. - // Must return a populated outfit — the client's parser NREs on an empty - // OutfitSelections (real RecNet never returns one), so serve a valid default. - return c.json(defaultAvatar) + return c.json((await getAvatar(c.env.DB, id)) ?? defaultAvatar) + }) + + // Save the player's avatar. [Authorize]. Stores the posted JSON payload verbatim + // on the account row and echoes it back. 400 on a non-object body; 404 when the + // caller has no account row to attach it to. + .post('/api/avatar/v2/set', async (c) => { + const id = await authedId(c) + if (id === null) return unauthorized(c) + const avatar = (await c.req.json().catch(() => null)) as Record | null + if (avatar === null || typeof avatar !== 'object' || Array.isArray(avatar)) { + return c.body(null, 400) + } + if (!(await setAvatar(c.env.DB, id, avatar))) return c.body(null, 404) + return c.json(avatar) }) // NUX checklist — the client fetches this on the econ host during load. [] diff --git a/apps/econ/src/test/integration/api.test.ts b/apps/econ/src/test/integration/api.test.ts index 3bdabbd..3bb8bf5 100644 --- a/apps/econ/src/test/integration/api.test.ts +++ b/apps/econ/src/test/integration/api.test.ts @@ -1,10 +1,28 @@ +import { env } from 'cloudflare:test' import { exports } from 'cloudflare:workers' -import { describe, expect, test } from 'vitest' +import { beforeAll, describe, expect, test } from 'vitest' import '../../econ.app' +import { SCHEMA_DDL } from '../../avatar-db' + +import type { Env } from '../../context' + +declare module 'cloudflare:test' { + interface ProvidedEnv extends Env {} +} + const ORIGIN = 'https://example.com' +// Build the accounts table and seed the test player (the default token's sub, 42) +// so avatar reads/writes have a row to attach to. +beforeAll(async () => { + for (const stmt of SCHEMA_DDL) await env.DB.prepare(stmt).run() + await env.DB.prepare('INSERT OR IGNORE INTO accounts (data) VALUES (?1)') + .bind(JSON.stringify({ AccountId: 42, Username: 'Tester', DisplayName: 'Tester' })) + .run() +}) + // Mint a token the way the `auth` worker does, using the same dev secret. const DEV_SECRET = 'dev-insecure-signing-key-change-me' @@ -41,13 +59,10 @@ describe('econ endpoints', () => { expect(body[0]).toHaveProperty('AvatarItemDesc') }) - test('GET /api/avatar/v1/defaultbaseavataritems returns the same catalog', async () => { + test('GET /api/avatar/v1/defaultbaseavataritems is an empty stub (no auth)', async () => { const res = await exports.default.fetch(`${ORIGIN}/api/avatar/v1/defaultbaseavataritems`) expect(res.status).toBe(200) - const body = (await res.json()) as unknown[] - expect(Array.isArray(body)).toBe(true) - expect(body.length).toBeGreaterThan(0) - expect(body[0]).toHaveProperty('AvatarItemDesc') + expect(await res.json()).toEqual([]) }) test('GET /api/avatar/v4/items 401s without a token', async () => { @@ -72,8 +87,11 @@ describe('econ endpoints', () => { expect(res.status).toBe(401) }) - test('GET /api/avatar/v2 returns a populated default avatar with a valid token', async () => { - const res = await exports.default.fetch(`${ORIGIN}/api/avatar/v2`, { headers: await bearer() }) + test('GET /api/avatar/v2 returns a populated default avatar when none is saved', async () => { + // Account 7 has no saved avatar → falls back to the default outfit. + const res = await exports.default.fetch(`${ORIGIN}/api/avatar/v2`, { + headers: await bearer('7'), + }) expect(res.status).toBe(200) const body = (await res.json()) as { OutfitSelections: string; FaceFeatures: string } // Must be non-empty — the client's outfit parser NREs on an empty string. @@ -82,10 +100,57 @@ describe('econ endpoints', () => { expect(body.FaceFeatures).toContain('eyeId') }) - test('GET /econ/customAvatarItems/v1/owned returns { items: [] } (no auth)', async () => { - const res = await exports.default.fetch(`${ORIGIN}/econ/customAvatarItems/v1/owned`) + test('POST /api/avatar/v2/set 401s without a token', async () => { + const res = await exports.default.fetch(`${ORIGIN}/api/avatar/v2/set`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ OutfitSelections: 'a,,0' }), + }) + expect(res.status).toBe(401) + }) + + test('POST /api/avatar/v2/set saves the avatar, and GET reads it back', async () => { + const headers = { ...(await bearer()), 'Content-Type': 'application/json' } + const avatar = { + OutfitSelections: '1fd69ef8-0b74-4962-af5a-67f0bf0358f2,,0;d0a9262f-5504-46a7-bb10-7507503db58e,,1', + OutfitSelectionsV2: '{"selections":[]}', + FaceFeatures: '{"eyeId":"AjGMoJhEcEehacRZjUMuDg"}', + SkinColor: '3529b670-a66d-448e-9573-1905eae5b9bf', + HairColor: '0e_jaaObREWTf1AorAZ95g', + CustomAvatarItems: [], + } + + // Save echoes the payload back. + const setRes = await exports.default.fetch(`${ORIGIN}/api/avatar/v2/set`, { + method: 'POST', + headers, + body: JSON.stringify(avatar), + }) + expect(setRes.status).toBe(200) + expect(await setRes.json()).toEqual(avatar) + + // And it persists — GET now returns the saved avatar, not the default. + const getRes = await exports.default.fetch(`${ORIGIN}/api/avatar/v2`, { headers: await bearer() }) + expect(await getRes.json()).toEqual(avatar) + }) + + test('POST /api/avatar/v2/set 404s when the caller has no account row', async () => { + const res = await exports.default.fetch(`${ORIGIN}/api/avatar/v2/set`, { + method: 'POST', + headers: { ...(await bearer('99999')), 'Content-Type': 'application/json' }, + body: JSON.stringify({ OutfitSelections: 'a,,0' }), + }) + expect(res.status).toBe(404) + }) + + test('GET /econ/customAvatarItems/v1/owned 401s without a token, returns an empty paginated stub', async () => { + const anon = await exports.default.fetch(`${ORIGIN}/econ/customAvatarItems/v1/owned`) + expect(anon.status).toBe(401) + const res = await exports.default.fetch(`${ORIGIN}/econ/customAvatarItems/v1/owned`, { + headers: await bearer(), + }) expect(res.status).toBe(200) - expect(await res.json()).toEqual({ items: [] }) + expect(await res.json()).toEqual({ Results: [], TotalResults: 0 }) }) test('GET /api/objectives/v1/myprogress returns the default progress (no auth)', async () => { diff --git a/apps/econ/worker-configuration.d.ts b/apps/econ/worker-configuration.d.ts index 82895af..b267e04 100644 --- a/apps/econ/worker-configuration.d.ts +++ b/apps/econ/worker-configuration.d.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -// Runtime types generated with workerd@1.20260317.1 2025-09-20 nodejs_compat +// Runtime types generated with workerd@1.20260625.1 2025-09-20 nodejs_compat // Begin runtime types /*! ***************************************************************************** Copyright (c) Cloudflare. All rights reserved. @@ -421,8 +421,12 @@ interface ExecutionContext { waitUntil(promise: Promise): void; passThroughOnException(): void; readonly props: Props; + cache?: CacheContext; + readonly access?: CloudflareAccessContext; + tracing: Tracing; } type ExportedHandlerFetchHandler = (request: Request>, env: Env, ctx: ExecutionContext) => Response | Promise; +type ExportedHandlerConnectHandler = (socket: Socket, env: Env, ctx: ExecutionContext) => void | Promise; type ExportedHandlerTailHandler = (events: TraceItem[], env: Env, ctx: ExecutionContext) => void | Promise; type ExportedHandlerTraceHandler = (traces: TraceItem[], env: Env, ctx: ExecutionContext) => void | Promise; type ExportedHandlerTailStreamHandler = (event: TailStream.TailEvent, env: Env, ctx: ExecutionContext) => TailStream.TailEventHandlerType | Promise; @@ -431,6 +435,7 @@ type ExportedHandlerQueueHandler = (controller: TestController, env: Env, ctx: ExecutionContext) => void | Promise; interface ExportedHandler { fetch?: ExportedHandlerFetchHandler; + connect?: ExportedHandlerConnectHandler; tail?: ExportedHandlerTailHandler; trace?: ExportedHandlerTraceHandler; tailStream?: ExportedHandlerTailStreamHandler; @@ -446,24 +451,50 @@ declare abstract class Navigator { sendBeacon(url: string, body?: BodyInit): boolean; readonly userAgent: string; readonly hardwareConcurrency: number; + readonly platform: string; readonly language: string; readonly languages: string[]; } interface AlarmInvocationInfo { readonly isRetry: boolean; readonly retryCount: number; + readonly scheduledTime: number; } interface Cloudflare { readonly compatibilityFlags: Record; } +interface CachePurgeError { + code: number; + message: string; +} +interface CachePurgeResult { + success: boolean; + errors: CachePurgeError[]; +} +interface CachePurgeOptions { + tags?: string[]; + pathPrefixes?: string[]; + purgeEverything?: boolean; +} +interface CacheContext { + purge(options: CachePurgeOptions): Promise; +} +interface CloudflareAccessContext { + readonly aud: string; + getIdentity(): Promise; +} +declare abstract class ColoLocalActorNamespace { + get(actorId: string): Fetcher; +} interface DurableObject { fetch(request: Request): Response | Promise; + connect?(socket: Socket): void | Promise; alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise; webSocketMessage?(ws: WebSocket, message: string | ArrayBuffer): void | Promise; webSocketClose?(ws: WebSocket, code: number, reason: string, wasClean: boolean): void | Promise; webSocketError?(ws: WebSocket, error: unknown): void | Promise; } -type DurableObjectStub = Fetcher & { +type DurableObjectStub = Fetcher & { readonly id: DurableObjectId; readonly name?: string; }; @@ -471,6 +502,7 @@ interface DurableObjectId { toString(): string; equals(other: DurableObjectId): boolean; readonly name?: string; + readonly jurisdiction?: string; } declare abstract class DurableObjectNamespace { newUniqueId(options?: DurableObjectNamespaceNewUniqueIdOptions): DurableObjectId; @@ -484,7 +516,7 @@ type DurableObjectJurisdiction = "eu" | "fedramp" | "fedramp-high"; interface DurableObjectNamespaceNewUniqueIdOptions { jurisdiction?: DurableObjectJurisdiction; } -type DurableObjectLocationHint = "wnam" | "enam" | "sam" | "weur" | "eeur" | "apac" | "oc" | "afr" | "me"; +type DurableObjectLocationHint = "wnam" | "enam" | "sam" | "weur" | "eeur" | "apac" | "apac-ne" | "apac-se" | "oc" | "afr" | "me"; type DurableObjectRoutingMode = "primary-only"; interface DurableObjectNamespaceGetDurableObjectOptions { locationHint?: DurableObjectLocationHint; @@ -498,6 +530,7 @@ interface DurableObjectState { readonly id: DurableObjectId; readonly storage: DurableObjectStorage; container?: Container; + facets: DurableObjectFacets; blockConcurrencyWhile(callback: () => Promise): Promise; acceptWebSocket(ws: WebSocket, tags?: string[]): void; getWebSockets(tag?: string): WebSocket[]; @@ -574,6 +607,16 @@ declare class WebSocketRequestResponsePair { get request(): string; get response(): string; } +interface DurableObjectFacets { + get(name: string, getStartupOptions: () => FacetStartupOptions | Promise>): Fetcher; + abort(name: string, reason: any): void; + delete(name: string): void; + clone(src: string, dst: string): void; +} +interface FacetStartupOptions { + id?: DurableObjectId | string; + class: DurableObjectClass; +} interface AnalyticsEngineDataset { writeDataPoint(event?: AnalyticsEngineDataPoint): void; } @@ -866,7 +909,7 @@ interface CustomEventCustomEventInit { * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob) */ declare class Blob { - constructor(type?: ((ArrayBuffer | ArrayBufferView) | string | Blob)[], options?: BlobOptions); + constructor(bits?: ((ArrayBuffer | ArrayBufferView) | string | Blob)[], options?: BlobOptions); /** * The **`size`** read-only property of the Blob interface returns the size of the Blob or File in bytes. * @@ -1862,8 +1905,31 @@ interface KVNamespaceGetWithMetadataResult { } type QueueContentType = "text" | "bytes" | "json" | "v8"; interface Queue { - send(message: Body, options?: QueueSendOptions): Promise; - sendBatch(messages: Iterable>, options?: QueueSendBatchOptions): Promise; + metrics(): Promise; + send(message: Body, options?: QueueSendOptions): Promise; + sendBatch(messages: Iterable>, options?: QueueSendBatchOptions): Promise; +} +interface QueueSendMetrics { + backlogCount: number; + backlogBytes: number; + oldestMessageTimestamp?: Date; +} +interface QueueSendMetadata { + metrics: QueueSendMetrics; +} +interface QueueSendResponse { + metadata: QueueSendMetadata; +} +interface QueueSendBatchMetrics { + backlogCount: number; + backlogBytes: number; + oldestMessageTimestamp?: Date; +} +interface QueueSendBatchMetadata { + metrics: QueueSendBatchMetrics; +} +interface QueueSendBatchResponse { + metadata: QueueSendBatchMetadata; } interface QueueSendOptions { contentType?: QueueContentType; @@ -1877,6 +1943,19 @@ interface MessageSendRequest { contentType?: QueueContentType; delaySeconds?: number; } +interface QueueMetrics { + backlogCount: number; + backlogBytes: number; + oldestMessageTimestamp?: Date; +} +interface MessageBatchMetrics { + backlogCount: number; + backlogBytes: number; + oldestMessageTimestamp?: Date; +} +interface MessageBatchMetadata { + metrics: MessageBatchMetrics; +} interface QueueRetryOptions { delaySeconds?: number; } @@ -1891,12 +1970,14 @@ interface Message { interface QueueEvent extends ExtendableEvent { readonly messages: readonly Message[]; readonly queue: string; + readonly metadata: MessageBatchMetadata; retryAll(options?: QueueRetryOptions): void; ackAll(): void; } interface MessageBatch { readonly messages: readonly Message[]; readonly queue: string; + readonly metadata: MessageBatchMetadata; retryAll(options?: QueueRetryOptions): void; ackAll(): void; } @@ -1915,7 +1996,7 @@ interface R2ListOptions { startAfter?: string; include?: ("httpMetadata" | "customMetadata")[]; } -declare abstract class R2Bucket { +interface R2Bucket { head(key: string): Promise; get(key: string, options: R2GetOptions & { onlyIf: R2Conditional | Headers; @@ -2580,6 +2661,11 @@ interface QueuingStrategyInit { */ highWaterMark: number; } +interface TracePreviewInfo { + id: string; + slug: string; + name: string; +} interface ScriptVersion { id?: string; tag?: string; @@ -2590,7 +2676,7 @@ declare abstract class TailEvent extends ExtendableEvent { readonly traces: TraceItem[]; } interface TraceItem { - readonly event: (TraceItemFetchEventInfo | TraceItemJsRpcEventInfo | TraceItemScheduledEventInfo | TraceItemAlarmEventInfo | TraceItemQueueEventInfo | TraceItemEmailEventInfo | TraceItemTailEventInfo | TraceItemCustomEventInfo | TraceItemHibernatableWebSocketEventInfo) | null; + readonly event: (TraceItemFetchEventInfo | TraceItemJsRpcEventInfo | TraceItemConnectEventInfo | TraceItemScheduledEventInfo | TraceItemAlarmEventInfo | TraceItemQueueEventInfo | TraceItemEmailEventInfo | TraceItemTailEventInfo | TraceItemCustomEventInfo | TraceItemHibernatableWebSocketEventInfo) | null; readonly eventTimestamp: number | null; readonly logs: TraceLog[]; readonly exceptions: TraceException[]; @@ -2600,6 +2686,8 @@ interface TraceItem { readonly scriptVersion?: ScriptVersion; readonly dispatchNamespace?: string; readonly scriptTags?: string[]; + readonly tailAttributes?: Record; + readonly preview?: TracePreviewInfo; readonly durableObjectId?: string; readonly outcome: string; readonly executionModel: string; @@ -2610,6 +2698,8 @@ interface TraceItem { interface TraceItemAlarmEventInfo { readonly scheduledTime: Date; } +interface TraceItemConnectEventInfo { +} interface TraceItemCustomEventInfo { } interface TraceItemScheduledEventInfo { @@ -3197,6 +3287,28 @@ interface EventSourceEventSourceInit { withCredentials?: boolean; fetcher?: Fetcher; } +interface ExecOutput { + readonly stdout: ArrayBuffer; + readonly stderr: ArrayBuffer; + readonly exitCode: number; +} +interface ContainerExecOptions { + cwd?: string; + env?: Record; + user?: string; + stdin?: ReadableStream | "pipe"; + stdout?: "pipe" | "ignore"; + stderr?: "pipe" | "ignore" | "combined"; +} +interface ExecProcess { + readonly stdin: WritableStream | null; + readonly stdout: ReadableStream | null; + readonly stderr: ReadableStream | null; + readonly pid: number; + readonly exitCode: Promise; + output(): Promise; + kill(signal?: number): void; +} interface Container { get running(): boolean; start(options?: ContainerStartupOptions): void; @@ -3207,12 +3319,40 @@ interface Container { setInactivityTimeout(durationMs: number | bigint): Promise; interceptOutboundHttp(addr: string, binding: Fetcher): Promise; interceptAllOutboundHttp(binding: Fetcher): Promise; + snapshotDirectory(options: ContainerDirectorySnapshotOptions): Promise; + snapshotContainer(options: ContainerSnapshotOptions): Promise; + interceptOutboundHttps(addr: string, binding: Fetcher): Promise; + exec(cmd: string[], options?: ContainerExecOptions): Promise; +} +interface ContainerDirectorySnapshot { + id: string; + size: number; + dir: string; + name?: string; +} +interface ContainerDirectorySnapshotOptions { + dir: string; + name?: string; +} +interface ContainerDirectorySnapshotRestoreParams { + snapshot: ContainerDirectorySnapshot; + mountPoint?: string; +} +interface ContainerSnapshot { + id: string; + size: number; + name?: string; +} +interface ContainerSnapshotOptions { + name?: string; } interface ContainerStartupOptions { entrypoint?: string[]; enableInternet: boolean; env?: Record; - hardTimeout?: (number | bigint); + labels?: Record; + directorySnapshots?: ContainerDirectorySnapshotRestoreParams[]; + containerSnapshot?: ContainerSnapshot; } /** * 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. @@ -3275,6 +3415,10 @@ type LoopbackDurableObjectClass DurableObjectClass : (opts: { props?: any; }) => DurableObjectClass); +interface LoopbackDurableObjectNamespace extends DurableObjectNamespace { +} +interface LoopbackColoLocalActorNamespace extends ColoLocalActorNamespace { +} interface SyncKvStorage { get(key: string): T | undefined; list(options?: SyncKvListOptions): Iterable<[ @@ -3294,9 +3438,11 @@ interface SyncKvListOptions { } interface WorkerStub { getEntrypoint(name?: string, options?: WorkerStubEntrypointOptions): Fetcher; + getDurableObjectClass(name?: string, options?: WorkerStubEntrypointOptions): DurableObjectClass; } interface WorkerStubEntrypointOptions { props?: any; + limits?: workerdResourceLimits; } interface WorkerLoader { get(name: string | null, getCode: () => WorkerLoaderWorkerCode | Promise): WorkerStub; @@ -3315,6 +3461,7 @@ interface WorkerLoaderWorkerCode { compatibilityDate: string; compatibilityFlags?: string[]; allowExperimental?: boolean; + limits?: workerdResourceLimits; mainModule: string; modules: Record; env?: any; @@ -3322,6 +3469,10 @@ interface WorkerLoaderWorkerCode { tails?: Fetcher[]; streamingTails?: Fetcher[]; } +interface workerdResourceLimits { + cpuMs?: number; + 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. @@ -3340,81 +3491,415 @@ declare abstract class Performance { */ toJSON(): object; } -// AI Search V2 API Error Interfaces +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; + Span: typeof Span; +} +declare abstract class Span { + get isTraced(): boolean; + setAttribute(key: string, value?: (boolean | number | string)): void; + end(): void; +} +/** + * Represents the identity of a user authenticated via Cloudflare Access. + * This matches the result of calling /cdn-cgi/access/get-identity. + * + * The exact structure of the returned object depends on the identity provider + * configuration for the Access application. The fields below represent commonly + * available properties, but additional provider-specific fields may be present. + */ +interface CloudflareAccessIdentity extends Record { + /** The user's email address, if available from the identity provider. */ + email?: string; + /** The user's display name. */ + name?: string; + /** The user's unique identifier. */ + user_uuid?: string; + /** The Cloudflare account ID. */ + account_id?: string; + /** Login timestamp (Unix epoch seconds). */ + iat?: number; + /** The user's IP address at authentication time. */ + ip?: string; + /** Authentication methods used (e.g., "pwd"). */ + amr?: string[]; + /** Identity provider information. */ + idp?: { + id: string; + type: string; + }; + /** Geographic information about where the user authenticated. */ + geo?: { + country: string; + }; + /** Group memberships from the identity provider. */ + groups?: Array<{ + id: string; + name: string; + email?: string; + }>; + /** Device posture check results, keyed by check ID. */ + devicePosture?: Record; + /** True if the user connected via Cloudflare WARP. */ + is_warp?: boolean; + /** True if the user is authenticated via Cloudflare Gateway. */ + is_gateway?: boolean; +} +// ============================================================================ +// Agent Memory +// +// Public type surface for user Workers binding to an Agent Memory namespace. +// ============================================================================ +/** Memory type — every memory is classified into exactly one. */ +type AgentMemoryMemoryType = "fact" | "event" | "instruction" | "task"; +/** Search intensity for recall. */ +type AgentMemoryThinkingLevel = "low" | "medium" | "high"; +/** Response verbosity for recall. */ +type AgentMemoryResponseLength = "short" | "medium" | "long"; +/** A conversation message passed to ingest(). */ +interface AgentMemoryMessage { + role: "system" | "user" | "assistant"; + content: string; + /** Optional message timestamp. */ + timestamp?: Date; +} +/** Raw memory content passed to remember(). */ +interface AgentMemoryIncomingMemory { + /** Raw memory content. The service classifies and summarizes automatically. */ + content: string; + /** Optional session identifier to associate with this memory. */ + sessionId?: string | null | undefined; +} +/** A stored memory returned from remember(), get(), and delete(). */ +interface AgentMemoryMemory { + /** Memory ID. */ + id: string; + /** Memory type. */ + type: AgentMemoryMemoryType; + /** Text summary. */ + summary: string; + /** Memory text. */ + content: string; + /** Session that created this memory. */ + sessionId: string | null; + /** Memory creation time. */ + createdAt: Date; + /** Memory last-update time. */ + updatedAt: Date; +} +/** Single entry in a list() response. Same shape as Memory minus full content. */ +type AgentMemoryMemoryListEntry = Omit; +/** A scored memory candidate in a recall result. */ +interface AgentMemoryScoredCandidate { + /** Candidate ID. */ + id: string; + /** Text summary. */ + summary: string; + /** Session that created this candidate, when known. */ + sessionId: string | null; + /** Relevance score (higher is better). Comparable only within a single query. */ + score: number; +} +/** Options for the ingest() method. */ +interface AgentMemoryIngestOptions { + /** Session identifier to associate with memories created during ingestion. */ + sessionId?: string | null | undefined; +} +/** Options for the getSummary() method. */ +interface AgentMemoryGetSummaryOptions { + /** Session identifier to retrieve session summary for. */ + sessionId?: string | null | undefined; +} +/** Response from the getSummary() method. */ +interface AgentMemoryGetSummaryResponse { + /** Markdown summary. */ + summary: string; +} +/** + * Options for the recall() method. + * + * `referenceDate` accepts a Date object, an ISO-8601 date string + * (YYYY-MM-DD), or a full ISO-8601 datetime string. When provided, this + * date is used as "today" for resolving relative time references + * ("how many days ago", "last week") instead of the server's wall-clock time. + */ +interface AgentMemoryRecallOptions { + /** Recall intensity: "low" (default), "medium", or "high". */ + thinkingLevel?: AgentMemoryThinkingLevel; + /** Response verbosity: "short", "medium" (default), or "long". */ + responseLength?: AgentMemoryResponseLength; + /** Temporal anchor for date arithmetic. */ + referenceDate?: Date | string; +} +/** Response from the recall() method. */ +interface AgentMemoryRecallResult { + /** Number of memories retrieved. */ + count: number; + /** LLM-generated answer synthesizing the matching memories. */ + answer: string; + /** Matching memories ranked by relevance. */ + candidates: AgentMemoryScoredCandidate[]; +} +/** + * Options for the list() method. + * + * `cursor` is the opaque continuation token returned by the previous page; + * pass it back unchanged to fetch the next page. `sessionId` and `type` + * are exact-match filters; combining them is allowed. + */ +interface AgentMemoryListMemoriesOptions { + /** Maximum number of memories to return. Default 20, max 500. */ + limit?: number; + /** Opaque cursor from a previous page. */ + cursor?: string; + /** Exact-match session filter. */ + sessionId?: string; + /** Exact-match memory-type filter. */ + type?: AgentMemoryMemoryType; +} +/** Response from the list() method. */ +interface AgentMemoryListMemoriesResult { + memories: AgentMemoryMemoryListEntry[]; + /** Continuation cursor; absent when this page exhausted the result set. */ + cursor?: string; +} +/** + * A single Agent Memory profile, scoped to a profile name. + * + * Returned by {@link AgentMemoryNamespace.getProfile}. + */ +declare abstract class AgentMemoryProfile { + /** + * Retrieve a memory by ID. + * + * @param memoryId - ULID of the memory to retrieve. + * @throws if the memory does not exist. + */ + get(memoryId: string): Promise; + /** + * Delete a memory by ID. + * + * Removes the memory and any source messages linked by the memory's + * source message IDs. + * + * @param memoryId - ULID of the memory to delete. + * @throws if the memory does not exist. + */ + delete(memoryId: string): Promise; + /** + * Store a memory in this profile. The content is automatically classified, + * summarized, and indexed. + * + * @param memory - Raw memory content to persist. + */ + remember(memory: AgentMemoryIncomingMemory): Promise; + /** + * Extract memories from a conversation. + * + * @param messages - Conversation messages to extract memories from. + * @param options - Optional ingest options. + */ + ingest(messages: Iterable, options?: AgentMemoryIngestOptions): Promise; + /** + * Get a profile summary. + * + * @param options - Optional getSummary options. + */ + getSummary(options?: AgentMemoryGetSummaryOptions): Promise; + /** + * Recall memories in this profile. + * + * @param query - Recall query matched against memory content and keywords. + * @param options - Optional recall parameters. + * @returns Matching memories with relevance scores and a synthesized answer. + */ + recall(query: string, options?: AgentMemoryRecallOptions): Promise; + /** + * List active memories in this profile. + * + * Returns a paginated, filterable view of stored memories. Superseded + * versions are excluded. Use the returned `cursor` (when present) to + * fetch the next page. + * + * @param options - Optional pagination and filter options. + */ + list(options?: AgentMemoryListMemoriesOptions): Promise; + /** + * Soft-delete every memory and message in this profile that is tagged + * with `sessionId`. + * + * Idempotent: deleting a sessionId that has no rows is a no-op. + * + * @param sessionId - Session to delete. + */ + deleteSession(sessionId: string): Promise; +} +/** + * Namespace-level Agent Memory binding. + * + * Used as the type of an `env.MEMORY`-style binding backed by the Agent + * Memory product. + * + * @example + * ```ts + * export default { + * async fetch(_request: Request, env: Env): Promise { + * const profile = await env.MEMORY.getProfile("wrangler-e2e"); + * const summary = await profile.getSummary(); + * return Response.json(summary); + * }, + * }; + * ``` + */ +declare abstract class AgentMemoryNamespace { + /** + * Get a memory profile by name. Profiles are isolated by namespace and + * addressed by a compound key (namespaceId:profileName). + * + * @param profileName - Profile name (validated against naming rules). + * @returns RPC target for interacting with the profile. + */ + getProfile(profileName: string): Promise; + /** + * Soft-delete a profile and schedule deferred purge. Marks all + * memories and messages as deleted. + * + * @param profileName - Name of the profile to delete. + */ + deleteProfile(profileName: string): Promise; +} +// ============ AI Search Error Interfaces ============ interface AiSearchInternalError extends Error { } interface AiSearchNotFoundError extends Error { } -interface AiSearchNameNotSetError extends Error { -} -// AI Search V2 Request Types -type AiSearchSearchRequest = { - messages: Array<{ - role: 'system' | 'developer' | 'user' | 'assistant' | 'tool'; - content: string | null; - }>; - ai_search_options?: { - retrieval?: { - retrieval_type?: 'vector' | 'keyword' | 'hybrid'; - /** Match threshold (0-1, default 0.4) */ - match_threshold?: number; - /** Maximum number of results (1-50, default 10) */ - max_num_results?: number; - filters?: VectorizeVectorMetadataFilter; - /** Context expansion (0-3, default 0) */ - context_expansion?: number; - [key: string]: unknown; - }; - query_rewrite?: { - enabled?: boolean; - model?: string; - rewrite_prompt?: string; - [key: string]: unknown; - }; - reranking?: { - /** Enable reranking (default false) */ - enabled?: boolean; - model?: '@cf/baai/bge-reranker-base' | ''; - /** Match threshold (0-1, default 0.4) */ - match_threshold?: number; - [key: string]: unknown; - }; +// ============ AI Search Common Types ============ +/** A single message in a conversation-style search or chat request. */ +type AiSearchMessage = { + role: 'system' | 'developer' | 'user' | 'assistant' | 'tool'; + content: string | null; +}; +/** + * Common shape for `ai_search_options` used by both single-instance and multi-instance requests. + * Contains retrieval, query rewrite, reranking, and cache sub-options. + */ +type AiSearchOptions = { + retrieval?: { + /** Which retrieval backend to use. Defaults to the instance's configured index_method. */ + retrieval_type?: 'vector' | 'keyword' | 'hybrid'; + /** Fusion method for combining vector + keyword results. */ + fusion_method?: 'max' | 'rrf'; + /** How keyword terms are combined: "and" = all terms must match, "or" = any term matches. */ + 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. */ + max_num_results?: number; + /** Vectorize metadata filters applied to the search. */ + filters?: VectorizeVectorMetadataFilter; + /** Number of surrounding chunks to include for context (0-3). Default 0. */ + context_expansion?: number; + /** If true, return only item metadata without chunk text. */ + metadata_only?: boolean; + /** If true (default), return empty results on retrieval failure instead of throwing. */ + return_on_failure?: boolean; + /** Boost results by metadata field values. Max 3 entries. */ + boost_by?: Array<{ + field: string; + direction?: 'asc' | 'desc' | 'exists' | 'not_exists'; + }>; [key: string]: unknown; }; -}; -type AiSearchChatCompletionsRequest = { - messages: Array<{ - role: 'system' | 'developer' | 'user' | 'assistant' | 'tool'; - content: string | null; - }>; - model?: string; - stream?: boolean; - ai_search_options?: { - retrieval?: { - retrieval_type?: 'vector' | 'keyword' | 'hybrid'; - match_threshold?: number; - max_num_results?: number; - filters?: VectorizeVectorMetadataFilter; - context_expansion?: number; - [key: string]: unknown; - }; - query_rewrite?: { - enabled?: boolean; - model?: string; - rewrite_prompt?: string; - [key: string]: unknown; - }; - reranking?: { - enabled?: boolean; - model?: '@cf/baai/bge-reranker-base' | ''; - match_threshold?: number; - [key: string]: unknown; - }; + query_rewrite?: { + enabled?: boolean; + model?: string; + rewrite_prompt?: string; [key: string]: unknown; }; + reranking?: { + enabled?: boolean; + model?: string; + /** Match threshold (0-1, default 0.4) */ + match_threshold?: number; + [key: string]: unknown; + }; + cache?: { + enabled?: boolean; + cache_threshold?: 'super_strict_match' | 'close_enough' | 'flexible_friend' | 'anything_goes'; + }; [key: string]: unknown; }; -// AI Search V2 Response Types +// ============ AI Search Request Types ============ +/** + * 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 AiSearchChatCompletionsRequest = { + messages: AiSearchMessage[]; + model?: string; + stream?: boolean; + ai_search_options?: AiSearchOptions; + [key: string]: unknown; +}; +// ============ AI Search Multi-Instance Types (Namespace-Scoped) ============ +/** `ai_search_options` shape for multi-instance requests — requires `instance_ids`. */ +type AiSearchMultiSearchOptions = AiSearchOptions & { + /** Instance IDs to search across (1-10). */ + instance_ids: string[]; +}; +/** + * Request for searching across multiple instances within a namespace. + * `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; +}; +/** A search result chunk tagged with the instance it originated from. */ +type AiSearchMultiSearchChunk = AiSearchSearchResponse['chunks'][number] & { + instance_id: string; +}; +/** Describes a per-instance error during a multi-instance operation. */ +type AiSearchMultiSearchError = { + instance_id: string; + message: string; +}; +/** Response from a multi-instance search, with chunks tagged by instance and optional partial-failure errors. */ +type AiSearchMultiSearchResponse = { + search_query: string; + chunks: AiSearchMultiSearchChunk[]; + 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 & { + ai_search_options: AiSearchMultiSearchOptions; +}; +/** Response from multi-instance chat completions, with chunks tagged by instance and optional partial-failure errors. */ +type AiSearchMultiChatCompletionsResponse = Omit & { + chunks: AiSearchMultiSearchChunk[]; + errors?: AiSearchMultiSearchError[]; +}; +// ============ AI Search Response Types ============ type AiSearchSearchResponse = { search_query: string; chunks: Array<{ @@ -3433,26 +3918,138 @@ type AiSearchSearchResponse = { keyword_score?: number; /** Vector similarity score (0-1) */ vector_score?: number; + /** Keyword rank position */ + keyword_rank?: number; + /** Vector rank position */ + vector_rank?: number; + /** Reranking model score */ + reranking_score?: number; + /** Fusion method used to combine results */ + fusion_method?: 'rrf' | 'max'; + [key: string]: unknown; }; }>; }; -type AiSearchListResponse = Array<{ - id: string; - internal_id?: string; - account_id?: string; - account_tag?: string; - /** Whether the instance is enabled (default true) */ - enable?: boolean; - type?: 'r2' | 'web-crawler'; - source?: string; +type AiSearchChatCompletionsResponse = { + id?: string; + object?: string; + model?: string; + choices: Array<{ + index?: number; + message: { + role: 'system' | 'developer' | 'user' | 'assistant' | 'tool'; + content: string | null; + [key: string]: unknown; + }; + [key: string]: unknown; + }>; + chunks: AiSearchSearchResponse['chunks']; [key: string]: unknown; -}>; +}; +type AiSearchStatsResponse = { + queued?: number; + running?: number; + completed?: number; + error?: number; + skipped?: number; + outdated?: number; + last_activity?: string; + /** Storage engine statistics. */ + engine?: { + vectorize?: { + vectorsCount: number; + dimensions: number; + }; + r2?: { + payloadSizeBytes: number; + metadataSizeBytes: number; + objectCount: number; + }; + }; +}; +// ============ AI Search Instance Info Types ============ +type AiSearchInstanceInfo = { + id: string; + type?: 'r2' | 'web-crawler' | string; + source?: string; + source_params?: unknown; + paused?: boolean; + status?: string; + namespace?: string; + created_at?: string; + modified_at?: string; + token_id?: string; + ai_gateway_id?: string; + rewrite_query?: boolean; + reranking?: boolean; + embedding_model?: string; + ai_search_model?: string; + rewrite_model?: string; + reranking_model?: string; + /** @deprecated Use index_method instead. */ + hybrid_search_enabled?: boolean; + /** Controls which storage backends are active. */ + index_method?: { + vector?: boolean; + keyword?: boolean; + }; + /** Fusion method for combining vector and keyword results. */ + fusion_method?: 'max' | 'rrf'; + indexing_options?: { + keyword_tokenizer?: 'porter' | 'trigram'; + } | null; + retrieval_options?: { + keyword_match_mode?: 'and' | 'or'; + boost_by?: Array<{ + field: string; + direction?: 'asc' | 'desc' | 'exists' | 'not_exists'; + }>; + } | null; + chunk?: boolean; + chunk_size?: number; + chunk_overlap?: number; + score_threshold?: number; + max_num_results?: number; + cache?: boolean; + cache_threshold?: 'super_strict_match' | 'close_enough' | 'flexible_friend' | 'anything_goes'; + custom_metadata?: Array<{ + field_name: string; + data_type: 'text' | 'number' | 'boolean' | 'datetime'; + }>; + /** Sync interval in seconds. */ + sync_interval?: 3600 | 7200 | 14400 | 21600 | 43200 | 86400; + metadata?: Record; + [key: string]: unknown; +}; +/** Pagination, search, and ordering parameters for listing instances within a namespace. */ +type AiSearchListInstancesParams = { + page?: number; + per_page?: number; + /** Search instances by ID. */ + search?: string; + /** Field to sort by. */ + order_by?: 'created_at'; + /** Sort direction. */ + order_by_direction?: 'asc' | 'desc'; +}; +type AiSearchListResponse = { + result: AiSearchInstanceInfo[]; + result_info?: { + count: number; + page: number; + per_page: number; + total_count: number; + }; +}; +// ============ AI Search Config Types ============ type AiSearchConfig = { /** Instance ID (1-32 chars, pattern: ^[a-z0-9_]+(?:-[a-z0-9_]+)*$) */ id: string; - type: 'r2' | 'web-crawler'; - source: string; - source_params?: object; + /** Instance type. Omit to create with built-in storage. */ + type?: 'r2' | 'web-crawler' | string; + /** Source URL (required for web-crawler type). */ + source?: string; + source_params?: unknown; /** Token ID (UUID format) */ token_id?: string; ai_gateway_id?: string; @@ -3462,52 +4059,460 @@ type AiSearchConfig = { reranking?: boolean; embedding_model?: string; ai_search_model?: string; -}; -type AiSearchInstance = { - id: string; - enable?: boolean; - type?: 'r2' | 'web-crawler'; - source?: string; + rewrite_model?: string; + reranking_model?: string; + /** @deprecated Use index_method instead. */ + hybrid_search_enabled?: boolean; + /** Controls which storage backends are used during indexing. Defaults to vector-only. */ + index_method?: { + vector?: boolean; + keyword?: boolean; + }; + /** Fusion method for combining vector and keyword results. "rrf" = reciprocal rank fusion (default), "max" = maximum score. */ + fusion_method?: 'max' | 'rrf'; + indexing_options?: { + keyword_tokenizer?: 'porter' | 'trigram'; + } | null; + retrieval_options?: { + keyword_match_mode?: 'and' | 'or'; + boost_by?: Array<{ + field: string; + direction?: 'asc' | 'desc' | 'exists' | 'not_exists'; + }>; + } | null; + chunk?: boolean; + chunk_size?: number; + chunk_overlap?: number; + /** Minimum similarity score (0-1) for a result to be included. */ + score_threshold?: number; + 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'; + custom_metadata?: Array<{ + field_name: string; + data_type: 'text' | 'number' | 'boolean' | 'datetime'; + }>; + namespace?: string; + /** Sync interval in seconds. 3600=1h, 7200=2h, 14400=4h, 21600=6h, 43200=12h, 86400=24h. */ + sync_interval?: 3600 | 7200 | 14400 | 21600 | 43200 | 86400; + metadata?: Record; [key: string]: unknown; }; -// AI Search Instance Service - Instance-level operations -declare abstract class AiSearchInstanceService { +// ============ AI Search Item Types ============ +type AiSearchItemInfo = { + id: string; + key: string; + status: 'completed' | 'error' | 'skipped' | 'queued' | 'running' | 'outdated'; + next_action?: 'INDEX' | 'DELETE' | null; + error?: string; + checksum?: string; + namespace?: string; + chunks_count?: number | null; + file_size?: number | null; + source_id?: string | null; + last_seen_at?: string; + created_at?: string; + metadata?: Record; + [key: string]: unknown; +}; +type AiSearchItemContentResult = { + body: ReadableStream; + contentType: string; + filename: string; + size: number; +}; +type AiSearchUploadItemOptions = { + metadata?: Record; +}; +type AiSearchListItemsParams = { + page?: number; + per_page?: number; + /** Search items by key name. */ + search?: string; + /** Sort order for results. */ + sort_by?: 'status' | 'modified_at'; + /** Filter items by processing status. */ + 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; +}; +type AiSearchListItemsResponse = { + result: AiSearchItemInfo[]; + result_info?: { + count: number; + page: number; + per_page: number; + total_count: number; + }; +}; +// ============ AI Search Item Logs Types ============ +type AiSearchItemLogsParams = { + /** Maximum number of log entries to return (1-100, default 50). */ + limit?: number; + /** Opaque cursor for pagination. Pass the `cursor` value from a previous response. */ + cursor?: string; +}; +type AiSearchItemLog = { + timestamp: string; + action: string; + message: string; + fileKey?: string; + chunkCount?: number; + processingTimeMs?: number; + errorType?: string; +}; +/** Paginated response for item processing logs (cursor-based). */ +type AiSearchItemLogsResponse = { + result: AiSearchItemLog[]; + result_info: { + count: number; + per_page: number; + cursor: string | null; + truncated: boolean; + }; +}; +// ============ AI Search Item Chunks Types ============ +type AiSearchItemChunksParams = { + /** Maximum number of chunks to return (1-100, default 20). */ + limit?: number; + /** Offset into the chunks list (default 0). */ + offset?: number; +}; +/** A single indexed chunk belonging to an item, including its text content and byte range. */ +type AiSearchItemChunk = { + id: string; + text: string; + start_byte: number; + end_byte: number; + item?: { + timestamp?: number; + key: string; + metadata?: Record; + }; +}; +/** Paginated response for item chunks (offset-based). */ +type AiSearchItemChunksResponse = { + result: AiSearchItemChunk[]; + result_info: { + count: number; + total: number; + limit: number; + offset: number; + }; +}; +// ============ AI Search Job Types ============ +type AiSearchJobInfo = { + id: string; + source: 'user' | 'schedule'; + description?: string; + last_seen_at?: string; + started_at?: string; + ended_at?: string; + end_reason?: string; +}; +type AiSearchJobLog = { + id: number; + message: string; + message_type: number; + created_at: number; +}; +type AiSearchCreateJobParams = { + description?: string; +}; +type AiSearchListJobsParams = { + page?: number; + per_page?: number; +}; +type AiSearchListJobsResponse = { + result: AiSearchJobInfo[]; + result_info?: { + count: number; + page: number; + per_page: number; + total_count: number; + }; +}; +type AiSearchJobLogsParams = { + page?: number; + per_page?: number; +}; +type AiSearchJobLogsResponse = { + result: AiSearchJobLog[]; + result_info?: { + count: number; + page: number; + per_page: number; + total_count: number; + }; +}; +// ============ AI Search Sub-Service Classes ============ +/** + * Single item service for an AI Search instance. + * Provides info, download, sync, logs, and chunks operations on a specific item. + */ +declare abstract class AiSearchItem { + /** Get metadata about this item. */ + info(): Promise; + /** + * Download the item's content. + * @returns Object with body stream, content type, filename, and size. + */ + download(): Promise; + /** + * Trigger re-indexing of this item. + * @returns The updated item info. + */ + sync(): Promise; + /** + * Retrieve processing logs for this item (cursor-based pagination). + * @param params Optional pagination parameters (limit, cursor). + * @returns Paginated log entries for this item. + */ + logs(params?: AiSearchItemLogsParams): Promise; + /** + * List indexed chunks for this item (offset-based pagination). + * @param params Optional pagination parameters (limit, offset). + * @returns Paginated chunk entries for this item. + */ + chunks(params?: AiSearchItemChunksParams): Promise; +} +/** + * Items collection service for an AI Search instance. + * Provides list, upload, and access to individual items. + */ +declare abstract class AiSearchItems { + /** List items in this instance. */ + list(params?: AiSearchListItemsParams): Promise; + /** + * Upload a file as an item. Behaves as an upsert: if an item with the same + * filename already exists, it is overwritten and re-indexed. + * @param name Filename for the uploaded item. + * @param content File content as a ReadableStream, Blob, or string. + * @param options Optional metadata to attach to the item. + * @returns The created item info. + */ + upload(name: string, content: ReadableStream | Blob | string, options?: AiSearchUploadItemOptions): Promise; + /** + * Upload a file and poll until processing completes. + * Behaves as an upsert: if an item with the same filename already exists, + * it is overwritten and re-indexed. + * @param name Filename for the uploaded item. + * @param content File content as a ReadableStream, Blob, or string. + * @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; + /** + * Get an item by ID. + * @param itemId The item identifier. + * @returns Item service for info, download, sync, logs, and chunks operations. + */ + get(itemId: string): AiSearchItem; + /** + * Delete an item from the instance. + * @param itemId The item identifier. + */ + delete(itemId: string): Promise; +} +/** + * Single job service for an AI Search instance. + * Provides info, logs, and cancel operations for a specific job. + */ +declare abstract class AiSearchJob { + /** Get metadata about this job. */ + info(): Promise; + /** Get logs for this job. */ + logs(params?: AiSearchJobLogsParams): Promise; + /** + * Cancel a running job. + * @returns The updated job info. + * @throws AiSearchNotFoundError if the job does not exist. + */ + cancel(): Promise; +} +/** + * Jobs collection service for an AI Search instance. + * Provides list, create, and access to individual jobs. + */ +declare abstract class AiSearchJobs { + /** List jobs for this instance. */ + list(params?: AiSearchListJobsParams): Promise; + /** + * Create a new indexing job. + * @param params Optional job parameters. + * @returns The created job info. + */ + create(params?: AiSearchCreateJobParams): Promise; + /** + * Get a job by ID. + * @param jobId The job identifier. + * @returns Job service for info, logs, and cancel operations. + */ + get(jobId: string): AiSearchJob; +} +// ============ AI Search Binding Classes ============ +/** + * Instance-level AI Search service. + * + * Used as: + * - The return type of `AiSearchNamespace.get(name)` (namespace binding) + * - The type of `env.BLOG_SEARCH` (single instance binding via `ai_search`) + * + * Provides search, chat, update, stats, items, and jobs operations. + * + * @example + * ```ts + * // Via namespace binding + * const instance = env.AI_SEARCH.get("blog"); + * const results = await instance.search({ + * query: "How does caching work?", + * }); + * + * // Via single instance binding + * const results = await env.BLOG_SEARCH.search({ + * messages: [{ role: "user", content: "How does caching work?" }], + * }); + * ``` + */ +declare abstract class AiSearchInstance { /** * Search the AI Search instance for relevant chunks. - * @param params Search request with messages and AI search options - * @returns Search response with matching chunks + * @param params Search request with query or messages and optional AI search options. + * @returns Search response with matching chunks and search query. */ search(params: AiSearchSearchRequest): Promise; + /** + * Generate chat completions with AI Search context (streaming). + * @param params Chat completions request with stream: true. + * @returns ReadableStream of server-sent events. + */ + chatCompletions(params: AiSearchChatCompletionsRequest & { + stream: true; + }): Promise; /** * Generate chat completions with AI Search context. - * @param params Chat completions request with optional streaming - * @returns Response object (if streaming) or chat completion result + * @param params Chat completions request. + * @returns Chat completion response with choices and RAG chunks. */ - chatCompletions(params: AiSearchChatCompletionsRequest): Promise; + chatCompletions(params: AiSearchChatCompletionsRequest): Promise; /** - * Delete this AI Search instance. + * Update the instance configuration. + * @param config Partial configuration to update. + * @returns Updated instance info. */ - delete(): Promise; + update(config: Partial): Promise; + /** Get metadata about this instance. */ + info(): Promise; + /** + * Get instance statistics (item count, indexing status, etc.). + * @returns Statistics with counts per status, last activity time, and engine details. + */ + stats(): Promise; + /** Items collection — list, upload, and manage items in this instance. */ + get items(): AiSearchItems; + /** Jobs collection — list, create, and inspect indexing jobs. */ + get jobs(): AiSearchJobs; } -// AI Search Account Service - Account-level operations -declare abstract class AiSearchAccountService { +/** + * Namespace-level AI Search service. + * + * Used as the type of `env.AI_SEARCH` (namespace binding via `ai_search_namespaces`). + * Scoped to a single namespace. Provides dynamic instance access, creation, deletion, + * and multi-instance search/chat operations. + * + * @example + * ```ts + * // Access an instance within the namespace + * const blog = env.AI_SEARCH.get("blog"); + * const results = await blog.search({ query: "How does caching work?" }); + * + * // List all instances in the namespace + * const instances = await env.AI_SEARCH.list(); + * + * // Create a new instance with built-in storage + * const tenant = await env.AI_SEARCH.create({ id: "tenant-123" }); + * + * // Upload items into the instance + * await tenant.items.upload("doc.pdf", fileContent); + * + * // Search across multiple instances + * const multi = await env.AI_SEARCH.search({ + * query: "caching", + * ai_search_options: { instance_ids: ["blog", "docs"] }, + * }); + * + * // Delete an instance + * await env.AI_SEARCH.delete("tenant-123"); + * ``` + */ +declare abstract class AiSearchNamespace { /** - * List all AI Search instances in the account. - * @returns Array of AI Search instances + * Get an instance by name within the bound namespace. + * @param name Instance name. + * @returns Instance service for search, chat, update, stats, items, and jobs. */ - list(): Promise; + get(name: string): AiSearchInstance; /** - * Get an AI Search instance by ID. - * @param name Instance ID - * @returns Instance service for performing operations + * List instances in the bound namespace. + * @param params Optional pagination, search, and ordering parameters. + * @returns Array of instance metadata with pagination info. */ - get(name: string): AiSearchInstanceService; + list(params?: AiSearchListInstancesParams): Promise; /** - * Create a new AI Search instance. - * @param config Instance configuration - * @returns Instance service for performing operations + * Create a new instance within the bound namespace. + * @param config Instance configuration. Only `id` is required — omit `type` and `source` to create with built-in storage. + * @returns Instance service for the newly created instance. + * + * @example + * ```ts + * // Create with built-in storage (upload items manually) + * const instance = await env.AI_SEARCH.create({ id: "my-search" }); + * + * // Create with web crawler source + * const instance = await env.AI_SEARCH.create({ + * id: "docs-search", + * type: "web-crawler", + * source: "https://developers.cloudflare.com", + * }); + * ``` */ - create(config: AiSearchConfig): Promise; + create(config: AiSearchConfig): Promise; + /** + * Delete an instance from the bound namespace. + * @param name Instance name to delete. + */ + delete(name: string): Promise; + /** + * Search across multiple instances within the bound namespace. + * Fans out to the specified instance_ids and merges results. + * @param params Search request with required `ai_search_options.instance_ids`. + * @returns Search response with chunks tagged by instance_id and optional partial-failure errors. + */ + search(params: AiSearchMultiSearchRequest): Promise; + /** + * Generate chat completions across multiple instances within the bound namespace (streaming). + * Fans out to the specified instance_ids, merges context, and generates a response. + * @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; + /** + * Generate chat completions across multiple instances within the bound namespace. + * Fans out to the specified instance_ids, merges context, and generates a response. + * @param params Chat completions request with required `ai_search_options.instance_ids`. + * @returns Chat completion response with choices, chunks tagged by instance_id, and optional partial-failure errors. + */ + chatCompletions(params: AiSearchMultiChatCompletionsRequest): Promise; } type AiImageClassificationInput = { image: number[]; @@ -3772,6 +4777,356 @@ declare abstract class BaseAiTranslation { inputs: AiTranslationInput; postProcessedOutputs: AiTranslationOutput; } +/** + * Workers AI support for OpenAI's Chat Completions API + */ +type ChatCompletionContentPartText = { + type: "text"; + text: string; +}; +type ChatCompletionContentPartImage = { + type: "image_url"; + image_url: { + url: string; + detail?: "auto" | "low" | "high"; + }; +}; +type ChatCompletionContentPartInputAudio = { + type: "input_audio"; + input_audio: { + /** Base64 encoded audio data. */ + data: string; + format: "wav" | "mp3"; + }; +}; +type ChatCompletionContentPartFile = { + type: "file"; + file: { + /** Base64 encoded file data. */ + file_data?: string; + /** The ID of an uploaded file. */ + file_id?: string; + filename?: string; + }; +}; +type ChatCompletionContentPartRefusal = { + type: "refusal"; + refusal: string; +}; +type ChatCompletionContentPart = ChatCompletionContentPartText | ChatCompletionContentPartImage | ChatCompletionContentPartInputAudio | ChatCompletionContentPartFile; +type FunctionDefinition = { + name: string; + description?: string; + parameters?: Record; + strict?: boolean | null; +}; +type ChatCompletionFunctionTool = { + type: "function"; + function: FunctionDefinition; +}; +type ChatCompletionCustomToolGrammarFormat = { + type: "grammar"; + grammar: { + definition: string; + syntax: "lark" | "regex"; + }; +}; +type ChatCompletionCustomToolTextFormat = { + type: "text"; +}; +type ChatCompletionCustomToolFormat = ChatCompletionCustomToolTextFormat | ChatCompletionCustomToolGrammarFormat; +type ChatCompletionCustomTool = { + type: "custom"; + custom: { + name: string; + description?: string; + format?: ChatCompletionCustomToolFormat; + }; +}; +type ChatCompletionTool = ChatCompletionFunctionTool | ChatCompletionCustomTool; +type ChatCompletionMessageFunctionToolCall = { + id: string; + type: "function"; + function: { + name: string; + /** JSON-encoded arguments string. */ + arguments: string; + }; +}; +type ChatCompletionMessageCustomToolCall = { + id: string; + type: "custom"; + custom: { + name: string; + input: string; + }; +}; +type ChatCompletionMessageToolCall = ChatCompletionMessageFunctionToolCall | ChatCompletionMessageCustomToolCall; +type ChatCompletionToolChoiceFunction = { + type: "function"; + function: { + name: string; + }; +}; +type ChatCompletionToolChoiceCustom = { + type: "custom"; + custom: { + name: string; + }; +}; +type ChatCompletionToolChoiceAllowedTools = { + type: "allowed_tools"; + allowed_tools: { + mode: "auto" | "required"; + tools: Array>; + }; +}; +type ChatCompletionToolChoiceOption = "none" | "auto" | "required" | ChatCompletionToolChoiceFunction | ChatCompletionToolChoiceCustom | ChatCompletionToolChoiceAllowedTools; +type DeveloperMessage = { + role: "developer"; + content: string | Array<{ + type: "text"; + text: string; + }>; + name?: string; +}; +type SystemMessage = { + role: "system"; + content: string | Array<{ + type: "text"; + text: string; + }>; + name?: string; +}; +/** + * Permissive merged content part used inside UserMessage arrays. + * + * Cabidela has a limitation where anyOf/oneOf with enum-based discrimination + * inside nested array items does not correctly match different branches for + * different array elements, so the schema uses a single merged object. + */ +type UserMessageContentPart = { + type: "text" | "image_url" | "input_audio" | "file"; + text?: string; + image_url?: { + url?: string; + detail?: "auto" | "low" | "high"; + }; + input_audio?: { + data?: string; + format?: "wav" | "mp3"; + }; + file?: { + file_data?: string; + file_id?: string; + filename?: string; + }; +}; +type UserMessage = { + role: "user"; + content: string | Array; + name?: string; +}; +type AssistantMessageContentPart = { + type: "text" | "refusal"; + text?: string; + refusal?: string; +}; +type AssistantMessage = { + role: "assistant"; + content?: string | null | Array; + refusal?: string | null; + name?: string; + audio?: { + id: string; + }; + tool_calls?: Array; + function_call?: { + name: string; + arguments: string; + }; +}; +type ToolMessage = { + role: "tool"; + content: string | Array<{ + type: "text"; + text: string; + }>; + tool_call_id: string; +}; +type FunctionMessage = { + role: "function"; + content: string; + name: string; +}; +type ChatCompletionMessageParam = DeveloperMessage | SystemMessage | UserMessage | AssistantMessage | ToolMessage | FunctionMessage; +type ChatCompletionsResponseFormatText = { + type: "text"; +}; +type ChatCompletionsResponseFormatJSONObject = { + type: "json_object"; +}; +type ResponseFormatJSONSchema = { + type: "json_schema"; + json_schema: { + name: string; + description?: string; + schema?: Record; + strict?: boolean | null; + }; +}; +type ResponseFormat = ChatCompletionsResponseFormatText | ChatCompletionsResponseFormatJSONObject | ResponseFormatJSONSchema; +type ChatCompletionsStreamOptions = { + include_usage?: boolean; + include_obfuscation?: boolean; +}; +type PredictionContent = { + type: "content"; + content: string | Array<{ + type: "text"; + text: string; + }>; +}; +type AudioParams = { + voice: string | { + id: string; + }; + format: "wav" | "aac" | "mp3" | "flac" | "opus" | "pcm16"; +}; +type WebSearchUserLocation = { + type: "approximate"; + approximate: { + city?: string; + country?: string; + region?: string; + timezone?: string; + }; +}; +type WebSearchOptions = { + search_context_size?: "low" | "medium" | "high"; + user_location?: WebSearchUserLocation; +}; +type ChatTemplateKwargs = { + /** Whether to enable reasoning, enabled by default. */ + enable_thinking?: boolean; + /** If false, preserves reasoning context between turns. */ + clear_thinking?: boolean; +}; +/** Shared optional properties used by both Prompt and Messages input branches. */ +type ChatCompletionsCommonOptions = { + model?: string; + audio?: AudioParams; + frequency_penalty?: number | null; + logit_bias?: Record | null; + logprobs?: boolean | null; + top_logprobs?: number | null; + max_tokens?: number | null; + max_completion_tokens?: number | null; + metadata?: Record | null; + modalities?: Array<"text" | "audio"> | null; + n?: number | null; + parallel_tool_calls?: boolean; + prediction?: PredictionContent; + presence_penalty?: number | null; + reasoning_effort?: "low" | "medium" | "high" | null; + chat_template_kwargs?: ChatTemplateKwargs; + response_format?: ResponseFormat; + seed?: number | null; + service_tier?: "auto" | "default" | "flex" | "scale" | "priority" | null; + stop?: string | Array | null; + store?: boolean | null; + stream?: boolean | null; + stream_options?: ChatCompletionsStreamOptions; + temperature?: number | null; + tool_choice?: ChatCompletionToolChoiceOption; + tools?: Array; + top_p?: number | null; + user?: string; + web_search_options?: WebSearchOptions; + function_call?: "none" | "auto" | { + name: string; + }; + functions?: Array; +}; +type PromptTokensDetails = { + cached_tokens?: number; + audio_tokens?: number; +}; +type CompletionTokensDetails = { + reasoning_tokens?: number; + audio_tokens?: number; + accepted_prediction_tokens?: number; + rejected_prediction_tokens?: number; +}; +type CompletionUsage = { + prompt_tokens: number; + completion_tokens: number; + total_tokens: number; + prompt_tokens_details?: PromptTokensDetails; + completion_tokens_details?: CompletionTokensDetails; +}; +type ChatCompletionTopLogprob = { + token: string; + logprob: number; + bytes: Array | null; +}; +type ChatCompletionTokenLogprob = { + token: string; + logprob: number; + bytes: Array | null; + top_logprobs: Array; +}; +type ChatCompletionAudio = { + id: string; + /** Base64 encoded audio bytes. */ + data: string; + expires_at: number; + transcript: string; +}; +type ChatCompletionUrlCitation = { + type: "url_citation"; + url_citation: { + url: string; + title: string; + start_index: number; + end_index: number; + }; +}; +type ChatCompletionResponseMessage = { + role: "assistant"; + content: string | null; + refusal: string | null; + annotations?: Array; + audio?: ChatCompletionAudio; + tool_calls?: Array; + function_call?: { + name: string; + arguments: string; + } | null; +}; +type ChatCompletionLogprobs = { + content: Array | null; + refusal?: Array | null; +}; +type ChatCompletionChoice = { + index: number; + message: ChatCompletionResponseMessage; + finish_reason: "stop" | "length" | "tool_calls" | "content_filter" | "function_call"; + logprobs: ChatCompletionLogprobs | null; +}; +type ChatCompletionsMessagesInput = { + messages: Array; +} & ChatCompletionsCommonOptions; +type ChatCompletionsOutput = { + id: string; + object: string; + created: number; + model: string; + choices: Array; + usage?: CompletionUsage; + system_fingerprint?: string | null; + service_tier?: "auto" | "default" | "flex" | "scale" | "priority" | null; +}; /** * Workers AI support for OpenAI's Responses API * Reference: https://github.com/openai/openai-node/blob/master/src/resources/responses/responses.ts @@ -4129,6 +5484,12 @@ type ReasoningEffort = "minimal" | "low" | "medium" | "high" | null; type StreamOptions = { include_obfuscation?: boolean; }; +/** Marks keys from T that aren't in U as optional never */ +type Without = { + [P in Exclude]?: never; +}; +/** 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[]; /** @@ -4399,10 +5760,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 { - /** - * Base64 encoded value of the audio data. - */ - audio: string; + audio: string | { + body?: object; + contentType?: string; + }; /** * Supported tasks are 'translate' or 'transcribe'. */ @@ -4420,9 +5781,33 @@ interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input { */ initial_prompt?: string; /** - * The prefix it appended the the beginning of the output of the transcription and can guide the transcription result. + * The prefix appended to the beginning of the output of the transcription and can guide the transcription result. */ prefix?: string; + /** + * The number of beams to use in beam search decoding. Higher values may improve accuracy at the cost of speed. + */ + beam_size?: number; + /** + * Whether to condition on previous text during transcription. Setting to false may help prevent hallucination loops. + */ + condition_on_previous_text?: boolean; + /** + * Threshold for detecting no-speech segments. Segments with no-speech probability above this value are skipped. + */ + no_speech_threshold?: number; + /** + * Threshold for filtering out segments with high compression ratio, which often indicate repetitive or hallucinated text. + */ + compression_ratio_threshold?: number; + /** + * Threshold for filtering out segments with low average log probability, indicating low confidence. + */ + log_prob_threshold?: number; + /** + * Optional threshold (in seconds) to skip silent periods that may cause hallucinations. + */ + hallucination_silence_threshold?: number; } interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output { transcription_info?: { @@ -4562,8 +5947,8 @@ interface Ai_Cf_Baai_Bge_M3_Input_Embedding_1 { */ truncate_inputs?: boolean; } -type Ai_Cf_Baai_Bge_M3_Output = Ai_Cf_Baai_Bge_M3_Ouput_Query | Ai_Cf_Baai_Bge_M3_Output_EmbeddingFor_Contexts | Ai_Cf_Baai_Bge_M3_Ouput_Embedding | Ai_Cf_Baai_Bge_M3_AsyncResponse; -interface Ai_Cf_Baai_Bge_M3_Ouput_Query { +type Ai_Cf_Baai_Bge_M3_Output = Ai_Cf_Baai_Bge_M3_Output_Query | Ai_Cf_Baai_Bge_M3_Output_EmbeddingFor_Contexts | Ai_Cf_Baai_Bge_M3_Output_Embedding | Ai_Cf_Baai_Bge_M3_AsyncResponse; +interface Ai_Cf_Baai_Bge_M3_Output_Query { response?: { /** * Index of the context in the request @@ -4583,7 +5968,7 @@ interface Ai_Cf_Baai_Bge_M3_Output_EmbeddingFor_Contexts { */ pooling?: "mean" | "cls"; } -interface Ai_Cf_Baai_Bge_M3_Ouput_Embedding { +interface Ai_Cf_Baai_Bge_M3_Output_Embedding { shape?: number[]; /** * Embeddings of the requested text values @@ -4686,7 +6071,7 @@ interface Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Messages { */ role?: string; /** - * 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 + * 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 | { @@ -4932,10 +6317,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; - /** - * The content of the message as a string. - */ - content: string; + content: string | { + /** + * Type of the content (text) + */ + type?: string; + /** + * Text content + */ + text?: string; + }[]; }[]; functions?: { name: string; @@ -5576,7 +6967,7 @@ interface Ai_Cf_Qwen_Qwq_32B_Messages { */ role?: string; /** - * 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 + * 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 | { @@ -5697,7 +7088,7 @@ interface Ai_Cf_Qwen_Qwq_32B_Messages { }; })[]; /** - * JSON schema that should be fulfilled for the response. + * JSON schema that should be fufilled for the response. */ guided_json?: object; /** @@ -5963,7 +7354,7 @@ interface Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Messages { }; })[]; /** - * JSON schema that should be fulfilled for the response. + * JSON schema that should be fufilled for the response. */ guided_json?: object; /** @@ -6054,7 +7445,7 @@ interface Ai_Cf_Google_Gemma_3_12B_It_Prompt { */ prompt: string; /** - * JSON schema that should be fulfilled for the response. + * JSON schema that should be fufilled for the response. */ guided_json?: object; /** @@ -6213,7 +7604,7 @@ interface Ai_Cf_Google_Gemma_3_12B_It_Messages { }; })[]; /** - * JSON schema that should be fulfilled for the response. + * JSON schema that should be fufilled for the response. */ guided_json?: object; /** @@ -6485,7 +7876,7 @@ interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages { })[]; response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode; /** - * JSON schema that should be fulfilled for the response. + * JSON schema that should be fufilled for the response. */ guided_json?: object; /** @@ -6715,7 +8106,7 @@ interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages_Inner { })[]; response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode; /** - * JSON schema that should be fulfilled for the response. + * JSON schema that should be fufilled for the response. */ guided_json?: object; /** @@ -6877,10 +8268,16 @@ interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages { * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). */ role: string; - /** - * The content of the message as a string. - */ - content: string; + content: string | { + /** + * Type of the content (text) + */ + type?: string; + /** + * Text content + */ + text?: string; + }[]; }[]; functions?: { name: string; @@ -7086,10 +8483,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; - /** - * The content of the message as a string. - */ - content: string; + content: string | { + /** + * Type of the content (text) + */ + type?: string; + /** + * Text content + */ + text?: string; + }[]; }[]; functions?: { name: string; @@ -7640,12 +9043,12 @@ declare abstract class Base_Ai_Cf_Pipecat_Ai_Smart_Turn_V2 { postProcessedOutputs: Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Output; } declare abstract class Base_Ai_Cf_Openai_Gpt_Oss_120B { - inputs: ResponsesInput; - postProcessedOutputs: ResponsesOutput; + inputs: XOR; + postProcessedOutputs: XOR; } declare abstract class Base_Ai_Cf_Openai_Gpt_Oss_20B { - inputs: ResponsesInput; - postProcessedOutputs: ResponsesOutput; + inputs: XOR; + postProcessedOutputs: XOR; } interface Ai_Cf_Leonardo_Phoenix_1_0_Input { /** @@ -7765,7 +9168,7 @@ interface Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Input { */ text: string | string[]; /** - * Target language to translate to + * 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"; } @@ -7844,10 +9247,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; - /** - * The content of the message as a string. - */ - content: string; + content: string | { + /** + * Type of the content (text) + */ + type?: string; + /** + * Text content + */ + text?: string; + }[]; }[]; functions?: { name: string; @@ -8053,10 +9462,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; - /** - * The content of the message as a string. - */ - content: string; + content: string | { + /** + * Type of the content (text) + */ + type?: string; + /** + * Text content + */ + text?: string; + }[]; }[]; functions?: { name: string; @@ -8552,6 +9967,74 @@ declare abstract class Base_Ai_Cf_Deepgram_Aura_2_Es { inputs: Ai_Cf_Deepgram_Aura_2_Es_Input; postProcessedOutputs: Ai_Cf_Deepgram_Aura_2_Es_Output; } +interface Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Input { + multipart: { + body?: object; + contentType?: string; + }; +} +interface Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Output { + /** + * Generated image as Base64 string. + */ + image?: string; +} +declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Dev { + inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Input; + postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Output; +} +interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Input { + multipart: { + body?: object; + contentType?: string; + }; +} +interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Output { + /** + * Generated image as Base64 string. + */ + image?: string; +} +declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B { + inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Input; + postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Output; +} +interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Input { + multipart: { + body?: object; + contentType?: string; + }; +} +interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Output { + /** + * Generated image as Base64 string. + */ + image?: string; +} +declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B { + inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Input; + postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Output; +} +declare abstract class Base_Ai_Cf_Zai_Org_Glm_4_7_Flash { + inputs: ChatCompletionsInput; + postProcessedOutputs: ChatCompletionsOutput; +} +declare abstract class Base_Ai_Cf_Moonshotai_Kimi_K2_5 { + inputs: ChatCompletionsInput; + postProcessedOutputs: ChatCompletionsOutput; +} +declare abstract class Base_Ai_Cf_Moonshotai_Kimi_K2_6 { + inputs: ChatCompletionsInput; + postProcessedOutputs: ChatCompletionsOutput; +} +declare abstract class Base_Ai_Cf_Nvidia_Nemotron_3_120B_A12B { + inputs: ChatCompletionsInput; + postProcessedOutputs: ChatCompletionsOutput; +} +declare abstract class Base_Ai_Cf_Google_Gemma_4_26B_A4B_IT { + inputs: ChatCompletionsInput; + postProcessedOutputs: ChatCompletionsOutput; +} interface AiModels { "@cf/huggingface/distilbert-sst-2-int8": BaseAiTextClassification; "@cf/stabilityai/stable-diffusion-xl-base-1.0": BaseAiTextToImage; @@ -8570,7 +10053,6 @@ interface AiModels { "@hf/thebloke/zephyr-7b-beta-awq": BaseAiTextGeneration; "@hf/thebloke/openhermes-2.5-mistral-7b-awq": BaseAiTextGeneration; "@hf/thebloke/neural-chat-7b-v3-1-awq": BaseAiTextGeneration; - "@hf/thebloke/llamaguard-7b-awq": BaseAiTextGeneration; "@hf/thebloke/deepseek-coder-6.7b-base-awq": BaseAiTextGeneration; "@hf/thebloke/deepseek-coder-6.7b-instruct-awq": BaseAiTextGeneration; "@cf/deepseek-ai/deepseek-math-7b-instruct": BaseAiTextGeneration; @@ -8637,6 +10119,14 @@ interface AiModels { "@cf/deepgram/flux": Base_Ai_Cf_Deepgram_Flux; "@cf/deepgram/aura-2-en": Base_Ai_Cf_Deepgram_Aura_2_En; "@cf/deepgram/aura-2-es": Base_Ai_Cf_Deepgram_Aura_2_Es; + "@cf/black-forest-labs/flux-2-dev": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Dev; + "@cf/black-forest-labs/flux-2-klein-4b": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B; + "@cf/black-forest-labs/flux-2-klein-9b": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B; + "@cf/zai-org/glm-4.7-flash": Base_Ai_Cf_Zai_Org_Glm_4_7_Flash; + "@cf/moonshotai/kimi-k2.5": Base_Ai_Cf_Moonshotai_Kimi_K2_5; + "@cf/moonshotai/kimi-k2.6": Base_Ai_Cf_Moonshotai_Kimi_K2_6; + "@cf/nvidia/nemotron-3-120b-a12b": Base_Ai_Cf_Nvidia_Nemotron_3_120B_A12B; + "@cf/google/gemma-4-26b-a4b-it": Base_Ai_Cf_Google_Gemma_4_26B_A4B_IT; } type AiOptions = { /** @@ -8662,6 +10152,7 @@ type AiOptions = { returnRawResponse?: boolean; prefix?: string; extraHeaders?: object; + signal?: AbortSignal; }; type AiModelsSearchParams = { author?: string; @@ -8688,64 +10179,61 @@ type AiModelsSearchObject = { value: string; }[]; }; +type ChatCompletionsBase = ChatCompletionsMessagesInput; +type ChatCompletionsInput = ChatCompletionsMessagesInput; interface InferenceUpstreamError extends Error { } interface AiInternalError extends Error { } type AiModelListType = Record; +type AiAsyncBatchResponse = { + request_id: string; +}; declare abstract class Ai { aiGatewayLogId: string | null; gateway(gatewayId: string): AiGateway; /** - * Access the AI Search API for managing AI-powered search instances. - * - * This is the new API that replaces AutoRAG with better namespace separation: - * - Account-level operations: `list()`, `create()` - * - Instance-level operations: `get(id).search()`, `get(id).chatCompletions()`, `get(id).delete()` - * - * @example - * ```typescript - * // List all AI Search instances - * const instances = await env.AI.aiSearch.list(); - * - * // Search an instance - * const results = await env.AI.aiSearch.get('my-search').search({ - * messages: [{ role: 'user', content: 'What is the policy?' }], - * ai_search_options: { - * retrieval: { max_num_results: 10 } - * } - * }); - * - * // Generate chat completions with AI Search context - * const response = await env.AI.aiSearch.get('my-search').chatCompletions({ - * messages: [{ role: 'user', content: 'What is the policy?' }], - * model: '@cf/meta/llama-3.3-70b-instruct-fp8-fast' - * }); - * ``` + * @deprecated Use the standalone `ai_search_namespaces` or `ai_search` Workers bindings instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ - aiSearch(): AiSearchAccountService; + aiSearch(): AiSearchNamespace; /** * @deprecated AutoRAG has been replaced by AI Search. - * Use `env.AI.aiSearch` instead for better API design and new features. + * Use the standalone `ai_search_namespaces` or `ai_search` Workers bindings instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ * - * Migration guide: - * - `env.AI.autorag().list()` → `env.AI.aiSearch.list()` - * - `env.AI.autorag('id').search({ query: '...' })` → `env.AI.aiSearch.get('id').search({ messages: [{ role: 'user', content: '...' }] })` - * - `env.AI.autorag('id').aiSearch(...)` → `env.AI.aiSearch.get('id').chatCompletions(...)` - * - * Note: The old API continues to work for backwards compatibility, but new projects should use AI Search. - * - * @see AiSearchAccountService - * @param autoragId Optional instance ID (omit for account-level operations) + * @param autoragId Instance ID */ autorag(autoragId: string): AutoRAG; - run(model: Name, inputs: InputOptions, options?: Options): Promise(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; + // WebSocket + run(model: Name, inputs: AiModelList[Name]['inputs'], options: AiOptions & { websocket: true; - } ? Response : InputOptions extends { + }): Promise; + // Streaming + run(model: Name, inputs: AiModelList[Name]['inputs'] & { stream: true; - } ? ReadableStream : AiModelList[Name]["postProcessedOutputs"]>; + }, options?: AiOptions): Promise; + // Normal (default) - known model + run(model: Name, inputs: AiModelList[Name]['inputs'], options?: AiOptions): Promise; + // Unknown model (fallback). + // + // The `Exclude<..., keyof AiModelList>` constraint forces TypeScript to + // route any model name that is a literal key of `AiModelList` to one of + // the known-model overloads above (so input/output mismatches surface as + // type errors rather than silently falling back to `Record`). + // Names that aren't in `AiModelList` — e.g. third-party gateway models + // like `"google/nano-banana"` — still hit this overload. + run(model: Model extends keyof AiModelList ? never : Model, inputs: Record, options?: AiOptions): Promise>; models(params?: AiModelsSearchParams): Promise; toMarkdown(): ToMarkdownService; toMarkdown(files: MarkdownDocument[], options?: ConversionRequestOptions): Promise; @@ -8843,29 +10331,250 @@ declare abstract class AiGateway { 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. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 /** - * @deprecated AutoRAG has been replaced by AI Search. Use AiSearchInternalError instead. - * @see AiSearchInternalError + * Artifacts — Git-compatible file storage on Cloudflare Workers. + * + * Provides programmatic access to create, manage, and fork repositories, + * and to issue and revoke scoped access tokens. + */ +/** Information about a repository. */ +interface ArtifactsRepoInfo { + /** Unique repository ID. */ + id: string; + /** Repository name. */ + name: string; + /** Repository description, or null if not set. */ + description: string | null; + /** Default branch name (e.g. "main"). */ + defaultBranch: string; + /** ISO 8601 creation timestamp. */ + createdAt: string; + /** ISO 8601 last-updated timestamp. */ + updatedAt: string; + /** ISO 8601 timestamp of the last push, or null if never pushed. */ + lastPushAt: string | null; + /** Fork source (e.g. "github:owner/repo", "artifacts:namespace/repo"), or null if not a fork. */ + source: string | null; + /** Whether the repository is read-only. */ + readOnly: boolean; + /** HTTPS git remote URL. */ + remote: string; +} +/** Result of creating a repository — includes the initial access token. */ +interface ArtifactsCreateRepoResult { + /** Unique repository ID. */ + id: string; + /** Repository name. */ + name: string; + /** Repository description, or null if not set. */ + description: string | null; + /** Default branch name. */ + defaultBranch: string; + /** HTTPS git remote URL. */ + remote: string; + /** Plaintext access token (only returned at creation time). */ + token: string; + /** ISO 8601 token expiry timestamp. */ + tokenExpiresAt: string; +} +/** Paginated list of repositories. */ +interface ArtifactsRepoListResult { + /** Repositories in this page (without the `remote` field). */ + repos: Omit[]; + /** Total number of repositories in the namespace. */ + total: number; + /** Cursor for the next page, if there are more results. */ + cursor?: string; +} +/** Result of creating an access token. */ +interface ArtifactsCreateTokenResult { + /** Unique token ID. */ + id: string; + /** Plaintext token (only returned at creation time). */ + plaintext: string; + /** Token scope: "read" or "write". */ + scope: 'read' | 'write'; + /** ISO 8601 token expiry timestamp. */ + expiresAt: string; +} +/** Token metadata (no plaintext). */ +interface ArtifactsTokenInfo { + /** Unique token ID. */ + id: string; + /** Token scope: "read" or "write". */ + scope: 'read' | 'write'; + /** Token state: "active", "expired", or "revoked". */ + state: 'active' | 'expired' | 'revoked'; + /** ISO 8601 creation timestamp. */ + createdAt: string; + /** ISO 8601 expiry timestamp. */ + expiresAt: string; +} +/** Paginated list of tokens for a repository. */ +interface ArtifactsTokenListResult { + /** Tokens in this page. */ + tokens: ArtifactsTokenInfo[]; + /** Total number of tokens for the repository. */ + total: number; +} +/** + * Handle for a single repository. Returned by Artifacts.get(). + * + * Methods may throw `ArtifactsError` with code `INTERNAL_ERROR` if an unexpected service error occurs. + */ +interface ArtifactsRepo extends ArtifactsRepoInfo { + /** + * Create an access token for this repo. + * @param scope Token scope: "write" (default) or "read". + * @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; + /** List tokens for this repo (metadata only, no plaintext). */ + listTokens(): Promise; + /** + * Revoke a token by plaintext or ID. + * @param tokenOrId Plaintext token or token ID. + * @returns true if revoked, false if not found. + * @throws {ArtifactsError} with code `INVALID_INPUT` if tokenOrId is empty. + */ + revokeToken(tokenOrId: string): Promise; + // ── Fork ── + /** + * Fork this repo to a new repo. + * @param name Target repository name. + * @param opts Optional: description, readOnly flag, defaultBranchOnly (default true). + * @throws {ArtifactsError} with code `INVALID_REPO_NAME` if name is invalid. + * @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; +} +// ── Error types ────────────────────────────────────────────────────────────── +/** + * Error codes returned by Artifacts binding operations. + * + * 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'; +/** + * Error thrown by Artifacts binding operations. + * + * Uses a string `.code` discriminator following the Cloudflare platform + * convention (StreamError, ImagesError, etc.). The `.numericCode` matches + * the REST API `errors[].code` values. + */ +interface ArtifactsError extends Error { + readonly name: 'ArtifactsError'; + /** String error code for programmatic matching. */ + readonly code: ArtifactsErrorCode; + /** Numeric error code matching the REST API. */ + readonly numericCode: number; +} +// ── Binding ────────────────────────────────────────────────────────────────── +/** + * Artifacts binding — namespace-level operations. + * + * Methods may throw `ArtifactsError` with code `INTERNAL_ERROR` if an unexpected service error occurs. + */ +interface Artifacts { + /** + * Create a new repository with an initial access token. + * @param name Repository name (alphanumeric, dots, hyphens, underscores). + * @param opts Optional: readOnly flag, description, default branch name. + * @returns Repo metadata with initial token. + * @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; + /** + * Get a handle to an existing repository. + * @param name Repository name. + * @returns Repo handle. + * @throws {ArtifactsError} with code `NOT_FOUND` if the repo does not exist. + * @throws {ArtifactsError} with code `IMPORT_IN_PROGRESS` if the repo is still importing. + * @throws {ArtifactsError} with code `FORK_IN_PROGRESS` if the repo is still forking. + */ + get(name: string): Promise; + /** + * Import a repository from an external git remote. + * @param params Source URL and optional branch/depth, plus target name and options. + * @returns Repo metadata with initial token. + * @throws {ArtifactsError} with code `INVALID_REPO_NAME` if the target name is invalid. + * @throws {ArtifactsError} with code `INVALID_INPUT` if the source URL is not valid HTTPS. + * @throws {ArtifactsError} with code `INVALID_URL` if the source URL does not point to a git repository. + * @throws {ArtifactsError} with code `REMOTE_AUTH_REQUIRED` if the remote requires authentication. + * @throws {ArtifactsError} with code `NOT_FOUND` if the remote repository does not exist. + * @throws {ArtifactsError} with code `UPSTREAM_UNAVAILABLE` if the remote cannot be reached. + * @throws {ArtifactsError} with code `MEMORY_LIMIT` if the import exceeds service memory limits. + * @throws {ArtifactsError} with code `ALREADY_EXISTS` if the target repo already exists. + */ + import(params: { + source: { + url: string; + branch?: string; + depth?: number; + }; + target: { + name: string; + opts?: { + description?: string; + readOnly?: boolean; + }; + }; + }): Promise; + /** + * 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; + /** + * Delete a repository and all associated tokens. + * @param name Repository name. + * @returns true if deleted, false if not found. + * @throws {ArtifactsError} with code `INVALID_REPO_NAME` if name is invalid. + */ + delete(name: string): Promise; +} +/** + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ interface AutoRAGInternalError extends Error { } /** - * @deprecated AutoRAG has been replaced by AI Search. Use AiSearchNotFoundError instead. - * @see AiSearchNotFoundError + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ interface AutoRAGNotFoundError extends Error { } /** - * @deprecated This error type is no longer used in the AI Search API. + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ interface AutoRAGUnauthorizedError extends Error { } /** - * @deprecated AutoRAG has been replaced by AI Search. Use AiSearchNameNotSetError instead. - * @see AiSearchNameNotSetError + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ interface AutoRAGNameNotSetError extends Error { } @@ -8879,9 +10588,8 @@ type CompoundFilter = { filters: ComparisonFilter[]; }; /** - * @deprecated AutoRAG has been replaced by AI Search. - * Use AiSearchSearchRequest with the new API instead. - * @see AiSearchSearchRequest + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ type AutoRagSearchRequest = { query: string; @@ -8898,26 +10606,23 @@ type AutoRagSearchRequest = { rewrite_query?: boolean; }; /** - * @deprecated AutoRAG has been replaced by AI Search. - * Use AiSearchChatCompletionsRequest with the new API instead. - * @see AiSearchChatCompletionsRequest + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ type AutoRagAiSearchRequest = AutoRagSearchRequest & { stream?: boolean; system_prompt?: string; }; /** - * @deprecated AutoRAG has been replaced by AI Search. - * Use AiSearchChatCompletionsRequest with stream: true instead. - * @see AiSearchChatCompletionsRequest + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ type AutoRagAiSearchRequestStreaming = Omit & { stream: true; }; /** - * @deprecated AutoRAG has been replaced by AI Search. - * Use AiSearchSearchResponse with the new API instead. - * @see AiSearchSearchResponse + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ type AutoRagSearchResponse = { object: 'vector_store.search_results.page'; @@ -8936,9 +10641,8 @@ type AutoRagSearchResponse = { next_page: string | null; }; /** - * @deprecated AutoRAG has been replaced by AI Search. - * Use AiSearchListResponse with the new API instead. - * @see AiSearchListResponse + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ type AutoRagListResponse = { id: string; @@ -8950,123 +10654,488 @@ type AutoRagListResponse = { status: string; }[]; /** - * @deprecated AutoRAG has been replaced by AI Search. - * The new API returns different response formats for chat completions. + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ type AutoRagAiSearchResponse = AutoRagSearchResponse & { response: string; }; /** - * @deprecated AutoRAG has been replaced by AI Search. - * Use the new AI Search API instead: `env.AI.aiSearch` - * - * Migration guide: - * - `env.AI.autorag().list()` → `env.AI.aiSearch.list()` - * - `env.AI.autorag('id').search(...)` → `env.AI.aiSearch.get('id').search(...)` - * - `env.AI.autorag('id').aiSearch(...)` → `env.AI.aiSearch.get('id').chatCompletions(...)` - * - * @see AiSearchAccountService - * @see AiSearchInstanceService + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ declare abstract class AutoRAG { /** - * @deprecated Use `env.AI.aiSearch.list()` instead. - * @see AiSearchAccountService.list + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ list(): Promise; /** - * @deprecated Use `env.AI.aiSearch.get(id).search(...)` instead. - * Note: The new API uses a messages array instead of a query string. - * @see AiSearchInstanceService.search + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ search(params: AutoRagSearchRequest): Promise; /** - * @deprecated Use `env.AI.aiSearch.get(id).chatCompletions(...)` instead. - * @see AiSearchInstanceService.chatCompletions + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ aiSearch(params: AutoRagAiSearchRequestStreaming): Promise; /** - * @deprecated Use `env.AI.aiSearch.get(id).chatCompletions(...)` instead. - * @see AiSearchInstanceService.chatCompletions + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ aiSearch(params: AutoRagAiSearchRequest): Promise; /** - * @deprecated Use `env.AI.aiSearch.get(id).chatCompletions(...)` instead. - * @see AiSearchInstanceService.chatCompletions + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ */ aiSearch(params: AutoRagAiSearchRequest): Promise; } -interface BasicImageTransformations { - /** - * Maximum width in image pixels. The value must be an integer. +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 `