diff --git a/.github/workflows/cloudflare.yml b/.github/workflows/cloudflare.yml new file mode 100644 index 0000000..4911034 --- /dev/null +++ b/.github/workflows/cloudflare.yml @@ -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 diff --git a/src/lib/ai-proxy.ts b/src/lib/ai-proxy.ts index 3cbdfcc..bca17f2 100644 --- a/src/lib/ai-proxy.ts +++ b/src/lib/ai-proxy.ts @@ -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> }>; 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 = { 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({ diff --git a/src/lib/anthropic-proxy.ts b/src/lib/anthropic-proxy.ts index 3ca3537..ea32c76 100644 --- a/src/lib/anthropic-proxy.ts +++ b/src/lib/anthropic-proxy.ts @@ -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 = {}; + 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 -------