From c7be8a48700126e86f9bb21f0eafe37bcaace8c2 Mon Sep 17 00:00:00 2001 From: AmirHossein Rezaei <78272016+DinonowDev@users.noreply.github.com> Date: Tue, 11 Aug 2026 15:28:34 +0330 Subject: [PATCH] fix(providers): drop dead Cloudflare Workers AI free catalog IDs (#8717) (#8804) Four of the original six free-catalog model IDs return 400/403/410 from Workers AI. Remove them from freeModelCatalog + cloudflare-ai registry, keep the live replacements from #8763, and move the 30M monthlyTokens budget onto @cf/meta/llama-3.3-70b-instruct-fp8-fast. Co-authored-by: MumuTW <42820974+MumuTW@users.noreply.github.com> --- open-sse/config/freeModelCatalog.data.ts | 6 +- .../providers/registry/cloudflare-ai/index.ts | 10 +- ...cloudflare-workers-ai-catalog-8717.test.ts | 101 ++++++++++++++++++ tests/unit/executor-cloudflare-ai.test.ts | 20 ++-- 4 files changed, 119 insertions(+), 18 deletions(-) create mode 100644 tests/unit/cloudflare-workers-ai-catalog-8717.test.ts diff --git a/open-sse/config/freeModelCatalog.data.ts b/open-sse/config/freeModelCatalog.data.ts index a9a09d5202..d5760f69a5 100644 --- a/open-sse/config/freeModelCatalog.data.ts +++ b/open-sse/config/freeModelCatalog.data.ts @@ -114,11 +114,11 @@ export const FREE_MODEL_BUDGETS: FreeModelBudget[] = [ { provider: "bytez", modelId: "Qwen/Qwen2.5-72B-Instruct", displayName: "Qwen/Qwen2.5-72B-Instruct", monthlyTokens: 0, creditTokens: 1000000, freeType: "recurring-credit", poolKey: "bytez", tos: "ambiguous" }, { provider: "cerebras", modelId: "zai-glm-4.7", displayName: "GLM 4.7", monthlyTokens: 30000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "cerebras", tos: "caution" }, { provider: "cerebras", modelId: "gpt-oss-120b", displayName: "GPT OSS 120B", monthlyTokens: 30000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "cerebras", tos: "caution" }, - { provider: "cloudflare-ai", modelId: "@cf/meta/llama-3.3-70b-instruct", displayName: "Llama 3.3 70B (🆓 ~150 resp/day)", monthlyTokens: 30000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "cloudflare-ai", tos: "caution" }, - { provider: "cloudflare-ai", modelId: "@cf/google/gemma-3-12b-it", displayName: "Gemma 3 12B (🆓)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-daily", poolKey: "cloudflare-ai", tos: "caution" }, + // #8717: drop dead Workers AI ids (400/403/410). Keep Neurons/day budget on fp8-fast. + { provider: "cloudflare-ai", modelId: "@cf/mistral/mistral-7b-instruct-v0.2-lora", displayName: "Mistral 7B (🆓)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-daily", poolKey: "cloudflare-ai", tos: "caution" }, { provider: "cloudflare-ai", modelId: "@cf/qwen/qwen2.5-coder-32b-instruct", displayName: "Qwen 2.5 Coder 32B (🆓)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-daily", poolKey: "cloudflare-ai", tos: "caution" }, { provider: "cloudflare-ai", modelId: "@cf/deepseek-ai/deepseek-r1-distill-qwen-32b", displayName: "DeepSeek R1 Distill 32B (🆓)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-daily", poolKey: "cloudflare-ai", tos: "caution" }, - { provider: "cloudflare-ai", modelId: "@cf/meta/llama-3.3-70b-instruct-fp8-fast", displayName: "Llama 3.3 70B (FP8 Fast 🆓)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-daily", poolKey: "cloudflare-ai", tos: "caution" }, + { provider: "cloudflare-ai", modelId: "@cf/meta/llama-3.3-70b-instruct-fp8-fast", displayName: "Llama 3.3 70B (FP8 Fast 🆓 ~150 resp/day)", monthlyTokens: 30000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "cloudflare-ai", tos: "caution" }, { provider: "cloudflare-ai", modelId: "@cf/meta/llama-3.2-3b-instruct", displayName: "Llama 3.2 3B (🆓)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-daily", poolKey: "cloudflare-ai", tos: "caution" }, { provider: "cloudflare-ai", modelId: "@cf/qwen/qwq-32b", displayName: "QwQ 32B (🆓)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-daily", poolKey: "cloudflare-ai", tos: "caution" }, { provider: "cloudflare-ai", modelId: "@cf/zai-org/glm-4.7-flash", displayName: "GLM 4.7 Flash (🆓)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-daily", poolKey: "cloudflare-ai", tos: "caution" }, diff --git a/open-sse/config/providers/registry/cloudflare-ai/index.ts b/open-sse/config/providers/registry/cloudflare-ai/index.ts index 907138aa48..5ab3eca452 100644 --- a/open-sse/config/providers/registry/cloudflare-ai/index.ts +++ b/open-sse/config/providers/registry/cloudflare-ai/index.ts @@ -9,14 +9,14 @@ export const cloudflare_aiProvider: RegistryEntry = { baseUrl: "https://api.cloudflare.com/client/v4/accounts", authType: "apikey", authHeader: "bearer", - // 10K Neurons/day free: ~150 LLM responses or 500s Whisper audio — global edge + // 10K Neurons/day free: ~150 LLM responses or 500s Whisper audio — global edge. + // #8717: omit dead ids (llama-3.3-70b-instruct, llama-3.1-8b-instruct, + // gemma-3-12b-it, qwen2.5-coder-15b-instruct) — Workers AI returns 400/403/410. models: [ - { id: "@cf/meta/llama-3.3-70b-instruct", name: "Llama 3.3 70B (🆓 ~150 resp/day)" }, - { id: "@cf/google/gemma-3-12b-it", name: "Gemma 3 12B (🆓)" }, + { id: "@cf/mistral/mistral-7b-instruct-v0.2-lora", name: "Mistral 7B (🆓)" }, { id: "@cf/qwen/qwen2.5-coder-32b-instruct", name: "Qwen 2.5 Coder 32B (🆓)" }, { id: "@cf/deepseek-ai/deepseek-r1-distill-qwen-32b", name: "DeepSeek R1 Distill 32B (🆓)" }, - // Sweep 2026-06-19: + current Workers AI catalog ids (developers.cloudflare.com/workers-ai/models). - { id: "@cf/meta/llama-3.3-70b-instruct-fp8-fast", name: "Llama 3.3 70B (FP8 Fast 🆓)" }, + { id: "@cf/meta/llama-3.3-70b-instruct-fp8-fast", name: "Llama 3.3 70B (FP8 Fast 🆓 ~150 resp/day)" }, { id: "@cf/meta/llama-3.2-3b-instruct", name: "Llama 3.2 3B (🆓)" }, { id: "@cf/qwen/qwq-32b", name: "QwQ 32B (🆓)" }, { diff --git a/tests/unit/cloudflare-workers-ai-catalog-8717.test.ts b/tests/unit/cloudflare-workers-ai-catalog-8717.test.ts new file mode 100644 index 0000000000..1f42854240 --- /dev/null +++ b/tests/unit/cloudflare-workers-ai-catalog-8717.test.ts @@ -0,0 +1,101 @@ +/** + * #8717 — Cloudflare Workers AI free catalog must not advertise dead model IDs. + * + * Four of the original six free-catalog entries return 400/403/410 from + * Workers AI (`No such model`, deprecated, or forbidden). #8763 added live + * replacements but left the dead IDs in place — free-model routing / combo + * builders still pick them and burn quota on guaranteed failures. + * + * Regression: dead IDs absent from FREE_MODEL_BUDGETS + cloudflare-ai registry; + * live replacements present; 30M monthlyTokens budget stays on the live + * Llama 3.3 70B FP8 Fast entry (poolKey cloudflare-ai dedupes by max). + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import { FREE_MODEL_BUDGETS } from "../../open-sse/config/freeModelCatalog.ts"; +import { cloudflare_aiProvider } from "../../open-sse/config/providers/registry/cloudflare-ai/index.ts"; + +const DEAD_CLOUDFLARE_MODEL_IDS = [ + "@cf/meta/llama-3.3-70b-instruct", + "@cf/meta/llama-3.1-8b-instruct", + "@cf/google/gemma-3-12b-it", + "@cf/qwen/qwen2.5-coder-15b-instruct", +] as const; + +const LIVE_CLOUDFLARE_MODEL_IDS = [ + "@cf/mistral/mistral-7b-instruct-v0.2-lora", + "@cf/deepseek-ai/deepseek-r1-distill-qwen-32b", + "@cf/qwen/qwen2.5-coder-32b-instruct", + "@cf/meta/llama-3.3-70b-instruct-fp8-fast", + "@cf/meta/llama-3.2-3b-instruct", + "@cf/qwen/qwq-32b", + "@cf/zai-org/glm-4.7-flash", + "@cf/moonshotai/kimi-k2.6", + "@cf/google/gemma-4-26b-a4b-it", +] as const; + +function cloudflareCatalogIds(): string[] { + return FREE_MODEL_BUDGETS.filter((m) => m.provider === "cloudflare-ai").map((m) => m.modelId); +} + +function cloudflareRegistryIds(): string[] { + assert.equal(cloudflare_aiProvider.id, "cloudflare-ai"); + return (cloudflare_aiProvider.models ?? []).map((m) => m.id); +} + +test("#8717: FREE_MODEL_BUDGETS must not list dead Cloudflare Workers AI model IDs", () => { + const ids = cloudflareCatalogIds(); + for (const dead of DEAD_CLOUDFLARE_MODEL_IDS) { + assert.equal( + ids.includes(dead), + false, + `dead model ${dead} must be removed from freeModelCatalog (still present)` + ); + } +}); + +test("#8717: FREE_MODEL_BUDGETS must list live Cloudflare Workers AI replacements", () => { + const ids = cloudflareCatalogIds(); + for (const live of LIVE_CLOUDFLARE_MODEL_IDS) { + assert.ok(ids.includes(live), `live model ${live} missing from freeModelCatalog`); + } +}); + +test("#8717: cloudflare-ai registry must not list dead model IDs", () => { + const ids = cloudflareRegistryIds(); + for (const dead of DEAD_CLOUDFLARE_MODEL_IDS) { + assert.equal( + ids.includes(dead), + false, + `dead model ${dead} must be removed from cloudflare-ai registry (still present)` + ); + } +}); + +test("#8717: cloudflare-ai registry must list live replacements", () => { + const ids = cloudflareRegistryIds(); + for (const live of LIVE_CLOUDFLARE_MODEL_IDS) { + assert.ok(ids.includes(live), `live model ${live} missing from cloudflare-ai registry`); + } +}); + +test("#8717: 30M monthlyTokens budget moves to live Llama 3.3 70B FP8 Fast", () => { + const fp8 = FREE_MODEL_BUDGETS.find( + (m) => + m.provider === "cloudflare-ai" && m.modelId === "@cf/meta/llama-3.3-70b-instruct-fp8-fast" + ); + assert.ok(fp8, "fp8-fast entry must exist"); + assert.equal( + fp8.monthlyTokens, + 30_000_000, + "Neurons/day free budget (~30M tok/mo) must stay on the live Llama 3.3 entry" + ); + + // No other cloudflare-ai catalog row should still claim the 30M (poolKey dedupes by max, + // but advertising it on a dead id was the original bug surface). + const withBudget = FREE_MODEL_BUDGETS.filter( + (m) => m.provider === "cloudflare-ai" && m.monthlyTokens === 30_000_000 + ); + assert.equal(withBudget.length, 1); + assert.equal(withBudget[0].modelId, "@cf/meta/llama-3.3-70b-instruct-fp8-fast"); +}); diff --git a/tests/unit/executor-cloudflare-ai.test.ts b/tests/unit/executor-cloudflare-ai.test.ts index b0fe2e7c71..1bef444ddc 100644 --- a/tests/unit/executor-cloudflare-ai.test.ts +++ b/tests/unit/executor-cloudflare-ai.test.ts @@ -5,7 +5,7 @@ import { CloudflareAIExecutor } from "../../open-sse/executors/cloudflare-ai.ts" test("CloudflareAIExecutor.buildUrl prefers providerSpecificData.accountId", () => { const executor = new CloudflareAIExecutor(); - const url = executor.buildUrl("@cf/meta/llama-3.3-70b-instruct", true, 0, { + const url = executor.buildUrl("@cf/meta/llama-3.3-70b-instruct-fp8-fast", true, 0, { accountId: "top-level-id", providerSpecificData: { accountId: "provider-id" }, }); @@ -22,10 +22,10 @@ test("CloudflareAIExecutor.buildUrl falls back to top-level credentials and envi process.env.CLOUDFLARE_ACCOUNT_ID = "env-account-id"; try { - const fromTopLevel = executor.buildUrl("@cf/meta/llama-3.3-70b-instruct", false, 0, { + const fromTopLevel = executor.buildUrl("@cf/meta/llama-3.3-70b-instruct-fp8-fast", false, 0, { accountId: "top-level-id", }); - const fromEnv = executor.buildUrl("@cf/meta/llama-3.3-70b-instruct", false, 0, {}); + const fromEnv = executor.buildUrl("@cf/meta/llama-3.3-70b-instruct-fp8-fast", false, 0, {}); assert.equal( fromTopLevel, @@ -48,7 +48,7 @@ test("CloudflareAIExecutor.buildUrl throws when account ID is missing", () => { try { assert.throws( - () => executor.buildUrl("@cf/meta/llama-3.3-70b-instruct", true, 0, {}), + () => executor.buildUrl("@cf/meta/llama-3.3-70b-instruct-fp8-fast", true, 0, {}), /Account ID/ ); } finally { @@ -76,10 +76,10 @@ test("CloudflareAIExecutor.buildHeaders uses API key or access token and stream test("CloudflareAIExecutor.transformRequest preserves plain-string content", () => { const executor = new CloudflareAIExecutor(); const body = { - model: "@cf/meta/llama-3.3-70b-instruct", + model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast", messages: [{ role: "user", content: "hi" }], }; - const out = executor.transformRequest("@cf/meta/llama-3.3-70b-instruct", body, true, {} as any); + const out = executor.transformRequest("@cf/meta/llama-3.3-70b-instruct-fp8-fast", body, true, {} as any); assert.deepEqual((out as any).messages, [{ role: "user", content: "hi" }]); }); @@ -88,7 +88,7 @@ test("CloudflareAIExecutor.transformRequest preserves plain-string content", () test("CloudflareAIExecutor.transformRequest flattens content-part arrays to strings (#2539)", () => { const executor = new CloudflareAIExecutor(); const body = { - model: "@cf/meta/llama-3.3-70b-instruct", + model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast", messages: [ { role: "user", @@ -101,7 +101,7 @@ test("CloudflareAIExecutor.transformRequest flattens content-part arrays to stri ], }; const out = executor.transformRequest( - "@cf/meta/llama-3.3-70b-instruct", + "@cf/meta/llama-3.3-70b-instruct-fp8-fast", body, false, {} as any @@ -132,11 +132,11 @@ test("CloudflareAIExecutor.execute uses inherited BaseExecutor flow successfully try { const body = { - model: "@cf/meta/llama-3.3-70b-instruct", + model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast", messages: [{ role: "user", content: "hello" }], }; const result = await executor.execute({ - model: "@cf/meta/llama-3.3-70b-instruct", + model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast", body, stream: false, credentials: {