Skip to content

Instantly share code, notes, and snippets.

@ericclemmons
Created September 6, 2025 22:39
Show Gist options
  • Save ericclemmons/d939830c7375c2f143645906cfb3fa62 to your computer and use it in GitHub Desktop.
Save ericclemmons/d939830c7375c2f143645906cfb3fa62 to your computer and use it in GitHub Desktop.
import alchemy from "alchemy";
import { CloudflareStateStore } from "alchemy/state";
const { AutoRag } = await import('./AutoRag.ts');
const app = await alchemy("orlie", {
stateStore: (scope) => new CloudflareStateStore(scope),
});
export const BUCKET = await R2Bucket('autorag-bucket');
export const GATEWAY = await AiGateway(`autorag-gateway`);
export const AUTO_RAG = await AutoRag(`autorag`, {
aiGateway: GATEWAY,
bucket: BUCKET,
});
await app.finalize();
// https://alchemy.run/concepts/resource/
import alchemy, { Resource, type Context } from "alchemy";
import {
CloudflareApi,
createCloudflareApi,
type AiGatewayResource,
type R2BucketResource,
} from "alchemy/cloudflare";
export interface AutoRagProps {
adopt?: boolean;
aiGateway: AiGatewayResource;
bucket: R2BucketResource;
enabled?: boolean;
name?: string;
// https://developers.cloudflare.com/api/resources/autorag/subresources/rags/methods/ai_search/#(params)%20default%20%3E%20(param)%20account_id%20%3E%20(schema)
aiSearchModel?:
| ""
| "@cf/meta/llama-3.3-70b-instruct-fp8-fast"
| "@cf/meta/llama-3.1-8b-instruct-fast"
| "@cf/meta/llama-3.1-8b-instruct-fp8"
| "@cf/meta/llama-4-scout-17b-16e-instruct"
| "@cf/qwen/qwen3-30b-a3b-fp8";
}
interface AutoRagResult {
ai_gateway_id: AiGatewayResource["id"];
ai_search_model: AutoRagProps["aiSearchModel"];
cache: boolean;
cache_threshold: "close_enough" | "exact_match" | null;
chunk: boolean;
chunk_overlap: number;
chunk_size: number;
created_at: string;
created_by: string;
embedding_model: string;
enabled?: boolean;
id: string;
max_num_results: number;
modified_at: string;
modified_by: string;
paused: boolean;
rewrite_model: string;
rewrite_query: boolean;
score_threshold: number;
source: R2BucketResource["name"];
source_params: { r2_jurisdiction: "default" };
status: "waiting" | "running" | "paused" | "error";
summarization: boolean;
summarization_model: string;
token_id: string;
type: "r2";
vectorize_name: string;
}
export interface AutoRagResource
extends Resource<"custom::cloudflare:autorag">,
AutoRagProps,
AutoRagResult {}
interface JsonResponse<T> {
success: boolean;
errors: { code: number; message: string }[];
messages: unknown[];
result: T;
}
interface PermissionGroup {
description: string;
id: string;
label: string;
name: string;
scopes: string[];
}
// ⚠️ Creating tokens requires special `API Tokens Write` permissions that aren't available in the UI!
// @see https://developers.cloudflare.com/api/resources/user/subresources/tokens/subresources/permission_groups/methods/list/
//
// As a workaround, we can use a global API token to get permission groups & create tokens.
// There's a template in the UI, but I haven't tested it:
// @see https://developers.cloudflare.com/fundamentals/api/reference/template/
//
// In theory, the user could call `await AutoRag({ tokenKey })`
const createTokenApi = async (api: CloudflareApi) => {
return new CloudflareApi({
accountId: api.accountId,
authOptions: {
apiKey: await alchemy.secret.env("CLOUDFLARE_GLOBAL_API_KEY"),
apiEmail: await alchemy.env("CLOUDFLARE_GLOBAL_EMAIL"),
type: "api-key",
},
});
};
const getPermissionGroups = async (api: CloudflareApi): Promise<PermissionGroup[]> => {
const globalApi = await createTokenApi(api);
const response = await globalApi.get(`/user/tokens/permission_groups`);
if (!response.ok) {
throw new Error("Failed to get permission groups", { cause: await response.json() });
}
const json = (await response.json()) as JsonResponse<PermissionGroup[]>;
if (!json.success) {
throw new Error("Failed to get permission groups", { cause: json });
}
return json.result;
};
type ResourceKey = `com.cloudflare.api.account.${string}`;
type Resources = Record<ResourceKey, string>;
interface Policy {
effect: "allow" | "deny";
id: string;
resources: Resources;
permission_groups: Array<Pick<PermissionGroup, "id" | "name">>;
}
interface UserToken {
id: string;
issued_on: string;
last_used_on: string | null;
modified_on: string;
name: string;
policies: Array<Policy>;
status: "active";
value: string;
}
const createUserToken = async (api: CloudflareApi, id: string, props: AutoRagProps) => {
const permissionGroups = await getPermissionGroups(api);
const condition = {} as const;
const name = `AutoRag Token - ${props.name ?? id}`;
const permissionGroupKeys = [] as const;
const scopes = [] as const;
const policies = [
{
effect: "allow",
resources: {
[`com.cloudflare.api.account.${api.accountId}`]: "*",
} satisfies Resources,
permission_groups: permissionGroups
.filter((group) =>
[
"AI Gateway Write",
"Vectorize Write",
"Workers R2 Storage Write",
"Workers Scripts Write",
"Account Settings Read",
].includes(group.name)
)
.map((group) => ({ id: group.id })),
},
];
const body = { name, scopes, permissionGroupKeys, condition, policies };
const tokenApi = await createTokenApi(api);
const response = await tokenApi.post(`/accounts/${api.accountId}/tokens`, body);
if (!response.ok) {
throw new Error("Failed to create user token", { cause: await response.json() });
}
const json = (await response.json()) as JsonResponse<UserToken>;
if (!json.success) {
throw new Error("Failed to create user token", { cause: json });
}
return json.result;
};
interface AutoRagToken {
cf_api_id: string;
cf_api_key: string;
created_at: string;
created_by: string;
id: string;
modified_at: string;
modified_by: string;
name: string;
}
const createAutoRagToken = async (
api: CloudflareApi,
_id: string,
_props: AutoRagProps,
userToken: UserToken
) => {
const body = {
cf_api_id: userToken.id,
cf_api_key: userToken.value,
name: userToken.name,
};
const response = await api.post(`/accounts/${api.accountId}/autorag/tokens`, body);
if (!response.ok) {
throw new Error("Failed to create AutoRag token", { cause: await response.json() });
}
const json = (await response.json()) as JsonResponse<AutoRagToken>;
if (!json.success) {
throw new Error("Failed to create AutoRag token", { cause: json });
}
return json.result;
};
const createAutoRag = async (api: CloudflareApi, id: string, props: AutoRagProps) => {
const userToken = await createUserToken(api, id, props);
const autoRagToken = await createAutoRagToken(api, id, props, userToken);
const body = {
ai_gateway_id: props.aiGateway.id,
ai_search_model: props.aiSearchModel ?? "", // Auto
cache: true,
cache_threshold: "close_enough",
chunk_overlap: 10,
chunk_size: 265,
embedding_model: "",
enabled: props.enabled ?? true,
id: props.name ?? id,
max_num_results: 10, // 1-10
rewrite_model: "",
rewrite_query: true,
score_threshold: 0.4,
source: props.bucket.name,
source_params: {
r2_jurisdiction: "default",
},
token_id: autoRagToken.id,
type: "r2",
} satisfies Omit<
AutoRagResult,
| "chunk"
| "created_at"
| "created_by"
| "modified_at"
| "modified_by"
| "paused"
| "status"
| "summarization_model"
| "summarization"
| "vectorize_name"
>;
// Proactively delete any previous vectorize indexes that got left behind by a previous AutoRag
const vectorizeResponse = await api.delete(
`/accounts/${api.accountId}/vectorize/v2/indexes/autorag-${body.id}`
);
if (vectorizeResponse.ok) {
console.info(
`Deleted previous Cloudflare AutoRag Vector Index "autorag-${body.id}" through API.`
);
}
const response = await api.post(`/accounts/${api.accountId}/autorag/rags`, body);
if (!response.ok) {
const cause = (await response.json()) as JsonResponse<unknown>;
if (
props.adopt &&
cause.errors.some((error) => error.message.includes("autorag_with_this_name_already_exist"))
) {
const existing = await api.get(`/accounts/${api.accountId}/autorag/rags/${body.id}`);
const json = (await existing.json()) as JsonResponse<AutoRagResource>;
if (!json.success) {
throw new Error("Failed to get existing AutoRag", { cause: json });
}
return json.result;
}
throw new Error("Failed to create AutoRag", { cause });
}
const json = (await response.json()) as JsonResponse<AutoRagResource>;
if (!json.success) {
throw new Error(
`Failed to create AutoRag. There may be previous resources conflicting at https://dash.cloudflare.com/${api.accountId}/ai/autorag`,
{ cause: json }
);
}
return json.result;
};
const updateAutoRag = async (
api: CloudflareApi,
_id: string,
_props: AutoRagProps,
output: AutoRagResource
) => {
console.warn("Don't feel like supporting AutoRag updates yet 😘. Returning existing value...");
const existing = await api.get(`/accounts/${api.accountId}/autorag/rags/${output.id}`);
const json = (await existing.json()) as JsonResponse<AutoRagResource>;
if (!json.success) {
throw new Error("Failed to get existing AutoRag", { cause: json });
}
return json.result;
};
const deleteAutoRag = async (
api: CloudflareApi,
id: string,
_props: AutoRagProps,
output: AutoRagResource
) => {
const response = await api.delete(
`/accounts/${api.accountId}/autorag/rags/${id}/${output.name}?force=true`
);
if (!response.ok) {
console.warn(
`Could not delete Cloudflare AutoRag through API. Do it manually at:\n\thttps://dash.cloudflare.com/${api.accountId}/ai/autorag`,
await response.json()
);
}
if (output.vectorize_name) {
const vectorizeResponse = await api.delete(
`/accounts/${api.accountId}/vectorize/v2/indexes/${output.vectorize_name}`
);
if (!vectorizeResponse.ok) {
console.warn(
`Could not delete Cloudflare AutoRag Vector Index "${output.vectorize_name}" through API. Do it manually at:\n\thttps://dash.cloudflare.com/${api.accountId}/ai/vectorize`,
await vectorizeResponse.json()
);
}
}
if (output.token_id) {
const tokenApi = await createTokenApi(api);
const tokenResponse = await tokenApi.delete(`/user/tokens/${output.token_id}`);
if (!tokenResponse.ok) {
console.warn(
`Could not delete Cloudflare AutoRag User Token "${output.token_id}" through API. Do it manually at:\n\thttps://dash.cloudflare.com/${api.accountId}/api-tokens`,
await tokenResponse.json()
);
}
}
};
export const AutoRag = Resource(
"custom::cloudflare:autorag",
async function (
this: Context<AutoRagResource>,
id: string,
props: AutoRagProps
): Promise<AutoRagResource> {
const api = await createCloudflareApi({});
switch (this.phase) {
case "create": {
const output = await createAutoRag(api, id, props);
return this.create({ ...props, ...output });
}
case "update": {
const output = await updateAutoRag(api, id, props, this.output);
return this.create({ ...props, ...output });
}
case "delete": {
await deleteAutoRag(api, id, props, this.output);
return this.destroy();
}
default:
// @ts-expect-error Property 'phase' does not exist on type 'never'.ts(2339)
const phase: never = this.phase;
throw new Error(`Unhandled phase: ${phase}`);
}
}
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment