Merge pull request #1 from MythEclipse/fix/anthropic-protocol

fix: anthropic messages validation, stream mapping, client error pars…
This commit is contained in:
Asep Haryana Saputra
2026-06-24 20:19:29 +07:00
committed by GitHub
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 { 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({
+41 -2
View File
@@ -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 -------