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:
co-authored by
Claude Opus 4.8
parent
e58a3ddaf8
commit
2171a01974
@@ -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
@@ -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({
|
||||
|
||||
@@ -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 -------
|
||||
|
||||
Reference in New Issue
Block a user