Building a First-Class Workers AI Provider for Laravel
/ 4 min read
Table of Contents
Why this started
Laravel is my AI-ready stack. Between Boost, skills, opinionated starter kits, and now the AI SDK — it’s the most productive environment I’ve found for shipping AI-powered products. I run multiple Laravel projects, each hitting different AI providers, and I needed centralized management across all of them.
I use Laravel Cloud for hosting, but I’ve been moving more and more of the surrounding infrastructure onto Cloudflare. Their AI Gateway sits between your app and any AI provider, giving you logging, caching, rate limiting, and cost visibility from a single dashboard. Point your base URL at the gateway, your existing API keys pass through, and you get full observability for free.
Naturally, the next step was Workers AI. Cloudflare runs open-source models (Llama, Qwen, Mistral) on their edge network with a generous free tier — 10,000 neurons/day. Perfect for chatbots, content suggestions, and internal tools where you don’t need GPT-4-level quality. The /compat endpoint speaks OpenAI’s /chat/completions format, so in theory, just point an OpenAI-compatible driver at it and go.
Where things broke
Theory met reality. Three bugs surfaced when using Prism’s xAI driver with Workers AI’s /compat endpoint:
1. Content format mismatch. The xAI MessageMap wraps every user message as [{type: "text", text: "..."}] — valid per the OpenAI spec, but Workers AI expects a plain string. Result: garbled responses or “Your input is not sufficient.”
2. Structured output crash. When you ask for JSON schema output, /compat sometimes returns content as an actual JSON object instead of a JSON string. The xAI driver tries to use it as a string → TypeError.
3. Missing content field. After a tool call, the assistant message has empty content. The xAI driver’s array_filter strips it out. Workers AI requires content to always be present, even when empty → 400 error on tool calling.
None of these are bugs in Prism or in Cloudflare individually. They’re compatibility gaps that only appear at the intersection.
What I built
First: the skill. I built a Claude Code skill that automates the entire AI Gateway setup for any Laravel project. It detects your framework (Prism PHP or Laravel AI SDK), asks a few questions, wires up gateway URLs for all your providers, and optionally adds Workers AI. One command, full setup. It works across Claude Code, Cursor, Gemini CLI — any agent that supports skills.
Then: the provider package. When the skill revealed those /compat compatibility gaps, I built meirdick/prism-workers-ai — a proper Prism provider following the same architecture as the official prism-php/bedrock. Custom handlers for text, structured output, streaming, embeddings, and a MessageMap that sends content in the format Workers AI actually expects. composer require and it works — text, structured output, tool calling, streaming, embeddings, all passing against live Workers AI calls across two real projects.
Then: the Laravel AI SDK gap. The package worked perfectly with Prism directly, but agent()->prompt(provider: 'workers-ai') threw an InvalidArgumentException. Turns out laravel/ai’s gateway layer hardcodes a driver-to-enum mapping — no external Prism provider can work through the agent system. Not even prism-php/bedrock with 100k+ downloads.
I filed laravel/ai#283 and submitted PR #284 — a one-line fix that falls back to the driver name as a string for unknown providers, letting Prism’s own resolution handle it. But upstream PRs take time, so the package ships with a PrismGateway subclass that bridges the gap today and auto-detects via reflection when the upstream fix lands. No config flags, no manual cleanup — composer update and the workaround turns itself off.
What I learned
This project changed how I think about Claude Code. I started using it to write code faster. Now I use it to engineer — to explore unfamiliar codebases, trace execution paths through three layers of abstraction, find architectural gaps, and design solutions that work with the framework instead of against it.
The reflection-based auto-detection, the service provider that registers with both PrismManager and AiManager, the gateway subclass that overrides exactly one method — none of that is code I would have written by hand in a reasonable timeframe. Not because it’s complex, but because the research to know what to write takes hours of reading framework internals. Claude Code did that research in minutes, and we iterated on the design together.
I’m not just shipping code faster. I’m shipping better-designed code, because I can explore more options before committing to one.
The ask
For @laravelphp: The one-line fix in PR #284 would make any external Prism provider work through the agent system. The extension point already exists in Prism. The AI SDK just needs to let it through.
For @CloudflareDev: The /compat endpoint is close to drop-in, but the string content requirement and object-typed structured output responses are real friction for SDK authors. Smoothing those edges would make Workers AI truly plug-and-play for the Laravel ecosystem.
Package: meirdick/prism-workers-ai — composer require meirdick/prism-workers-ai
Skill: meirdick/laravel-cloudflare-ai-gateway