From fd2ee238ac3d712d61993df2e02ac84f19f32e05 Mon Sep 17 00:00:00 2001 From: morgan-coded <256248948+morgan-coded@users.noreply.github.com> Date: Thu, 9 Jul 2026 22:34:23 -0500 Subject: [PATCH] fix(tanstack-ai): omit empty Workers AI tools arrays --- .changeset/empty-workers-tools.md | 5 ++ .../tanstack-ai/src/utils/create-fetcher.ts | 7 ++- .../tanstack-ai/test/binding-fetch.test.ts | 18 +++++++ .../tanstack-ai/test/gateway-fetch.test.ts | 53 +++++++++++++++++++ 4 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 .changeset/empty-workers-tools.md diff --git a/.changeset/empty-workers-tools.md b/.changeset/empty-workers-tools.md new file mode 100644 index 000000000..a70234ec5 --- /dev/null +++ b/.changeset/empty-workers-tools.md @@ -0,0 +1,5 @@ +--- +"@cloudflare/tanstack-ai": patch +--- + +fix(tanstack-ai): omit empty Workers AI tools arrays diff --git a/packages/tanstack-ai/src/utils/create-fetcher.ts b/packages/tanstack-ai/src/utils/create-fetcher.ts index 8f5b4b9ba..bb4b8a0f2 100644 --- a/packages/tanstack-ai/src/utils/create-fetcher.ts +++ b/packages/tanstack-ai/src/utils/create-fetcher.ts @@ -283,6 +283,9 @@ export function createGatewayFetch( } delete query.model; delete query.instructions; + if (Array.isArray(query.tools) && query.tools.length === 0) { + delete query.tools; + } } if (config.apiKey) { @@ -448,7 +451,9 @@ function buildBindingInputs(body: Record): Record[]); } - if (body.tools) inputs.tools = body.tools; + if (body.tools && (!Array.isArray(body.tools) || body.tools.length > 0)) { + inputs.tools = body.tools; + } if (typeof body.temperature === "number") inputs.temperature = body.temperature; if (typeof body.max_tokens === "number") inputs.max_tokens = body.max_tokens; if (body.response_format) inputs.response_format = body.response_format; diff --git a/packages/tanstack-ai/test/binding-fetch.test.ts b/packages/tanstack-ai/test/binding-fetch.test.ts index fe99d7c8c..f2b7054bb 100644 --- a/packages/tanstack-ai/test/binding-fetch.test.ts +++ b/packages/tanstack-ai/test/binding-fetch.test.ts @@ -347,6 +347,24 @@ describe("createWorkersAiBindingFetch", () => { ]); }); + it("should omit empty tools from binding inputs", async () => { + const binding = mockBinding(vi.fn().mockResolvedValue({ response: "ok" })); + + const fetcher = createWorkersAiBindingFetch(binding); + + await fetcher("https://api.openai.com/v1/chat/completions", { + method: "POST", + body: JSON.stringify({ + model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast", + messages: [], + tools: [], + }), + }); + + const [, inputs] = binding.run.mock.calls[0]!; + expect(inputs).not.toHaveProperty("tools"); + }); + it("should normalize null content to empty string in messages", async () => { const binding = mockBinding(vi.fn().mockResolvedValue({ response: "ok" })); const fetcher = createWorkersAiBindingFetch(binding); diff --git a/packages/tanstack-ai/test/gateway-fetch.test.ts b/packages/tanstack-ai/test/gateway-fetch.test.ts index 4e2fced93..05cc134bb 100644 --- a/packages/tanstack-ai/test/gateway-fetch.test.ts +++ b/packages/tanstack-ai/test/gateway-fetch.test.ts @@ -136,6 +136,18 @@ describe("createGatewayFetch", () => { const request = mockBinding.run.mock.calls[0]![0]; expect(request.query).toEqual({ _raw: "not-json-content" }); }); + + it("should preserve empty tools for non-Workers AI providers", async () => { + const fetcher = createGatewayFetch("openai", bindingConfig); + + await fetcher("https://api.openai.com/v1/chat/completions", { + method: "POST", + body: JSON.stringify({ model: "gpt-4o", messages: [], tools: [] }), + }); + + const request = mockBinding.run.mock.calls[0]![0]; + expect(request.query.tools).toEqual([]); + }); }); describe("credentials config", () => { @@ -359,6 +371,47 @@ describe("createGatewayFetch", () => { expect(request.query.messages).toEqual([]); }); + it("should omit empty tools from query", async () => { + const config: AiGatewayAdapterConfig = { + binding: mockBinding, + apiKey: "test-key", + }; + const fetcher = createGatewayFetch("workers-ai", config); + + await fetcher("https://api.openai.com/v1/chat/completions", { + method: "POST", + body: JSON.stringify({ + model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast", + messages: [], + tools: [], + }), + }); + + const request = mockBinding.run.mock.calls[0]![0]; + expect(request.query).not.toHaveProperty("tools"); + }); + + it("should preserve non-empty tools in query", async () => { + const config: AiGatewayAdapterConfig = { + binding: mockBinding, + apiKey: "test-key", + }; + const fetcher = createGatewayFetch("workers-ai", config); + const tools = [{ type: "function", function: { name: "add", parameters: {} } }]; + + await fetcher("https://api.openai.com/v1/chat/completions", { + method: "POST", + body: JSON.stringify({ + model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast", + messages: [], + tools, + }), + }); + + const request = mockBinding.run.mock.calls[0]![0]; + expect(request.query.tools).toEqual(tools); + }); + it("should not double-prefix run/ when URL path already contains it", async () => { const config: AiGatewayAdapterConfig = { binding: mockBinding,