A Cloudflare Worker that proxies an upstream Azure OpenAI Chat Completion API and exposes two client-facing surfaces:
- The standard OpenAI Chat Completion API (
/v1/chat/completions) - The new OpenAI Responses API (
/v1/responses)
Clients can talk to your Azure deployment using either the original Chat Completion shape or the newer Responses API shape without knowing anything about Azure-specific URLs or auth headers.
- Dual upstream endpoints: works against the official Azure OpenAI endpoint (
https://<resource>.openai.azure.com) or any compatible reverse proxy that speaks the Azure Chat Completion URL shape - Dual upstream auth:
api-keyheader (default Azure) orAuthorization: Bearer(Entra ID / reverse proxies) - Chat Completion passthrough with auth/URL rewriting
- Responses API ↔ Chat Completion conversion, both streaming and non-streaming
- Streaming SSE with proper
response.created/response.output_item.added/response.output_text.delta/response.completedevent semantics and strictly-increasingsequence_numbers - Tool calling — flat Responses tool defs are translated to nested Chat Completion tool defs, and
function_call/function_call_outputitems round-trip correctly - Reasoning models —
reasoning.effortand non-standardreasoning_contentdeltas are surfaced asreasoningoutput items - Usage — Chat Completion usage is mapped to
input_tokens/output_tokens/output_tokens_details.reasoning_tokens - Stream optimizer — typewriter effect smoothing for both surfaces
- ShadowFetch — raw socket-level upstream fetch that drops Workers' default
cf-*headers (useful for picky enterprise gateways) - CORS preflight handled
- API key protection for the worker itself
Copy wrangler.toml.example to wrangler.toml:
[vars]
# Upstream: official Azure OpenAI endpoint or a compatible reverse proxy
AZURE_ENDPOINT = "https://your-resource.openai.azure.com"
AZURE_API_VERSION = "2024-10-21"
AZURE_AUTH_HEADER = "api-key" # or "authorization" (Bearer)
# Defaults
DEFAULT_MODEL = "gpt-4o"
EXPOSED_MODELS = "gpt-4o,gpt-4o-mini,gpt-5,o3-mini"
ENABLE_STREAMING = true
DEBUG_LOG = falseSecrets (set with wrangler secret put):
| Secret | Required | Purpose |
|---|---|---|
AZURE_API_KEY |
yes | Upstream auth token sent in api-key or Authorization: Bearer |
API_KEY |
no | Bearer token clients must send to the worker |
Standard OpenAI Chat Completion request/response. The body is passed through to Azure largely unchanged; only stream_options.include_usage is forced when streaming so usage can be threaded into the response.
curl -X POST https://your-worker.workers.dev/v1/chat/completions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o",
"messages": [{"role":"user","content":"Hello!"}],
"stream": false
}'The model field maps to the Azure deployment name (not the underlying model id).
New OpenAI Responses API. The worker translates the request into a Chat Completion call against Azure and translates the response back.
curl -X POST https://your-worker.workers.dev/v1/responses \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o",
"instructions": "Be concise.",
"input": "What is 2+2?"
}'Streaming:
curl -N -X POST https://your-worker.workers.dev/v1/responses \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o",
"input": [
{"type":"message","role":"user","content":[
{"type":"input_text","text":"Tell me a joke"}
]}
],
"stream": true
}'Supported request fields:
model,input(string or array of items:message,function_call,function_call_output,reasoning)instructions(mapped to a leadingsystemmessage)tools(functiononly — hosted tools likeweb_searchare dropped)tool_choice(string, or{type:"function", name})max_output_tokens→max_completion_tokensreasoning.effort→reasoning_efforttext.verbosity→verbositytext.format(incl.json_schema) →response_formattemperature,top_p,top_logprobs,parallel_tool_calls,metadata,user,store,service_tier,stream
Supported response output items:
messagewithoutput_textand optionalrefusalfunction_call(withcall_id,name,arguments)reasoning(when upstream returns the non-standardreasoning_contentfield)
The full streaming envelope is preserved: lifecycle events (response.created, response.in_progress, response.completed / response.incomplete), item events (response.output_item.added/done), content-part events (response.content_part.added/done), text deltas (response.output_text.delta/done), function-call arg deltas (response.function_call_arguments.delta/done), and reasoning summary events.
Returns the deployments listed in EXPOSED_MODELS in OpenAI models.list format.
Protect the worker with API_KEY:
npx wrangler secret put API_KEYClients must then send Authorization: Bearer <API_KEY>. If API_KEY is unset, the worker is open.
npm install
npm run dev # wrangler dev
npm test # vitest
npm run typecheck # tsc --noEmit
npm run deploy # wrangler deployThe Responses API has server-side state via previous_response_id. This worker is stateless and does not implement that conversation rehydration — clients must send the full input array on each turn. The previous_response_id field is accepted and echoed back in the response snapshot but not consulted.
MIT