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 {
|
export interface OpenAIRequest {
|
||||||
model: string;
|
model: string;
|
||||||
messages: Array<{ role: string; content: string }>;
|
messages: Array<{ role: string; content: string | Array<Record<string, unknown>> }>;
|
||||||
temperature?: number;
|
temperature?: number;
|
||||||
max_tokens?: number;
|
max_tokens?: number;
|
||||||
top_p?: number;
|
top_p?: number;
|
||||||
|
t top_k?: number;
|
||||||
stream?: boolean;
|
stream?: boolean;
|
||||||
stop?: string | string[];
|
stop?: string | string[];
|
||||||
presence_penalty?: number;
|
presence_penalty?: number;
|
||||||
@@ -192,7 +193,7 @@ export const MODEL_ROUTES: Record<string, BackendConfig> = {
|
|||||||
const hasMarker = messages.some(
|
const hasMarker = messages.some(
|
||||||
(m) =>
|
(m) =>
|
||||||
m.role === "system" &&
|
m.role === "system" &&
|
||||||
m.content.includes("You are MiMoCode"),
|
(typeof m.content === "string" ? m.content.includes("You are MiMoCode") : false),
|
||||||
);
|
);
|
||||||
if (!hasMarker) {
|
if (!hasMarker) {
|
||||||
messages.unshift({
|
messages.unshift({
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ export interface AnthropicRequest {
|
|||||||
stream?: boolean;
|
stream?: boolean;
|
||||||
temperature?: number;
|
temperature?: number;
|
||||||
top_p?: number;
|
top_p?: number;
|
||||||
|
t top_k?: number;
|
||||||
|
stream?: boolean;
|
||||||
top_k?: number;
|
top_k?: number;
|
||||||
stop_sequences?: string[];
|
stop_sequences?: string[];
|
||||||
system?: string | AnthropicSystemBlock[];
|
system?: string | AnthropicSystemBlock[];
|
||||||
@@ -86,6 +88,8 @@ interface BackendBody {
|
|||||||
max_tokens: number;
|
max_tokens: number;
|
||||||
temperature?: number;
|
temperature?: number;
|
||||||
top_p?: number;
|
top_p?: number;
|
||||||
|
t top_k?: number;
|
||||||
|
stream?: boolean;
|
||||||
top_k?: number;
|
top_k?: number;
|
||||||
stream?: boolean;
|
stream?: boolean;
|
||||||
stop?: string | string[];
|
stop?: string | string[];
|
||||||
@@ -187,6 +191,10 @@ export function anthropicToBackend(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (config.adaptRequest) {
|
if (config.adaptRequest) {
|
||||||
|
const headers: Record<string, string> = {};
|
||||||
|
if (anthropicVersion) {
|
||||||
|
headers["anthropic-version"] = anthropicVersion;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
body: config.adaptRequest({
|
body: config.adaptRequest({
|
||||||
model: backendModel,
|
model: backendModel,
|
||||||
@@ -194,8 +202,13 @@ export function anthropicToBackend(
|
|||||||
temperature: anthReq.temperature,
|
temperature: anthReq.temperature,
|
||||||
max_tokens: anthReq.max_tokens,
|
max_tokens: anthReq.max_tokens,
|
||||||
top_p: anthReq.top_p,
|
top_p: anthReq.top_p,
|
||||||
|
top_k: anthReq.top_k,
|
||||||
stream: anthReq.stream,
|
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 ------------------------------------
|
// -- Handle error responses from backend ------------------------------------
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const status = response.status;
|
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) {
|
if (sessionPool && sessionId) {
|
||||||
sessionPool.release(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 -------
|
// -- For native Anthropic passthrough, relay the raw backend response -------
|
||||||
|
|||||||
Reference in New Issue
Block a user