fix: anthropic messages validation, stream mapping, client error parsing, and add Cloudflare Workers deployment workflow

- Fix type mappings in OpenAIRequest and BackendConfig to handle structured message parts (cache-control).
- Stop adaptRequest crashing when message contents are objects or arrays.
- Forward stream, top_k, and stop_sequences to adaptRequest.
- Forward anthropic-version header to backend endpoints during adaptation.
- Parse structured JSON error payloads from upstream before returning generic "Upstream rejected request" error.
- Create .github/workflows/cloudflare.yml to auto-deploy on master branch pushes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MythEclipse
2026-06-24 19:37:23 +07:00
co-authored by Claude Opus 4.8
parent e58a3ddaf8
commit 2171a01974
3 changed files with 76 additions and 4 deletions
+32
View File
@@ -0,0 +1,32 @@
name: Deploy to Cloudflare Workers
on:
push:
branches:
- master
workflow_dispatch:
jobs:
deploy:
name: Deploy to Cloudflare Workers
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install Dependencies
run: bun install --frozen-lockfile
- name: Run Tests
run: bun test
- name: Deploy Worker
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: deploy
+3 -2
View File
@@ -22,10 +22,11 @@ import * as aichatAuth from "./aichat-auth";
export interface OpenAIRequest {
model: string;
messages: Array<{ role: string; content: string }>;
messages: Array<{ role: string; content: string | Array<Record<string, unknown>> }>;
temperature?: number;
max_tokens?: number;
top_p?: number;
t top_k?: number;
stream?: boolean;
stop?: string | string[];
presence_penalty?: number;
@@ -192,7 +193,7 @@ export const MODEL_ROUTES: Record<string, BackendConfig> = {
const hasMarker = messages.some(
(m) =>
m.role === "system" &&
m.content.includes("You are MiMoCode"),
(typeof m.content === "string" ? m.content.includes("You are MiMoCode") : false),
);
if (!hasMarker) {
messages.unshift({
+41 -2
View File
@@ -40,6 +40,8 @@ export interface AnthropicRequest {
stream?: boolean;
temperature?: number;
top_p?: number;
t top_k?: number;
stream?: boolean;
top_k?: number;
stop_sequences?: string[];
system?: string | AnthropicSystemBlock[];
@@ -86,6 +88,8 @@ interface BackendBody {
max_tokens: number;
temperature?: number;
top_p?: number;
t top_k?: number;
stream?: boolean;
top_k?: number;
stream?: boolean;
stop?: string | string[];
@@ -187,6 +191,10 @@ export function anthropicToBackend(
}
if (config.adaptRequest) {
const headers: Record<string, string> = {};
if (anthropicVersion) {
headers["anthropic-version"] = anthropicVersion;
}
return {
body: config.adaptRequest({
model: backendModel,
@@ -194,8 +202,13 @@ export function anthropicToBackend(
temperature: anthReq.temperature,
max_tokens: anthReq.max_tokens,
top_p: anthReq.top_p,
top_k: anthReq.top_k,
stream: anthReq.stream,
stop: anthReq.stop_sequences?.length === 1
? anthReq.stop_sequences[0]
: anthReq.stop_sequences,
}),
headers,
};
}
@@ -798,11 +811,37 @@ export async function handleAnthropicMessages(
// -- Handle error responses from backend ------------------------------------
if (!response.ok) {
const status = response.status;
const genericMsg = status >= 500 ? "Upstream server error" : "Upstream rejected request";
// Read the upstream error body so clients see the actual rejection reason,
// not just a generic "Upstream rejected request" message.
let upstreamMsg = status >= 500 ? "Upstream server error" : "Upstream rejected request";
try {
const errBody = await response.text();
if (errBody) {
const errJson = JSON.parse(errBody);
// OpenAI-style: { error: { message, type } }
if (errJson?.error?.message) {
upstreamMsg = errJson.error.message;
}
// Anthropic-style: { type: "error", error: { message, type } }
else if (errJson?.type === "error" && errJson?.error?.message) {
upstreamMsg = errJson.error.message;
}
// Plain JSON with message field
else if (errJson?.message) {
upstreamMsg = errJson.message;
}
// Raw text error body
else if (typeof errBody === "string" && errBody.length < 500) {
upstreamMsg = errBody;
}
}
} catch {
// Could not read error body — keep generic message
}
if (sessionPool && sessionId) {
sessionPool.release(sessionId);
}
return anthropicError(status, genericMsg, "upstream_error");
return anthropicError(status, upstreamMsg, "upstream_error");
}
// -- For native Anthropic passthrough, relay the raw backend response -------