mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
This commit is contained in:
committed by
GitHub
parent
6812b62f4a
commit
d76153c7bb
@@ -10,6 +10,7 @@ _Living section — bullets land here as PRs merge into `release/v3.8.47` (paral
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **fix(providers):** `cloudflare-ai` no longer silently drops image/non-text content parts ([#6390](https://github.com/diegosouzapw/OmniRoute/issues/6390)) — `transformRequest()`'s `flattenContent()` (added for #2539 to satisfy the Workers AI `/ai/v1/chat/completions` plain-string `content` requirement) mapped any non-text OpenAI content part (e.g. `image_url`) to `""` and joined the rest, so a request carrying an image quietly went out as text-only with the attachment gone and no error surfaced. It now throws a clear error on the first non-text part instead of dropping it silently, which the existing top-level `chatCore.ts` catch already routes through `buildErrorBody()`/`sanitizeErrorMessage()` (same pattern as `buildUrl()`'s missing-Account-ID error). Regression guard: `tests/unit/cloudflare-ai-image-parts-6390.test.ts`.
|
||||
- **fix(providers):** stop Antigravity connections from falsely reporting **all-accounts quota-exhausted** ([#6295](https://github.com/diegosouzapw/OmniRoute/issues/6295)) — `genericQuotaFetcher.ts::percentUsedForQuota()` ignored the `fractionReported` flag and defaulted an unreported model's `remainingPercentage` to 0, which computed as 100% used; since `convertUsageToQuotaInfo()` takes the worst-case window across a connection, a single model with no reported fraction dragged the whole account into `limitReached` and `quotaPreflight` skipped it. `percentUsedForQuota()` now returns `null` (unknown, window ignored) whenever `fractionReported === false`, before falling back to `remainingPercentage`. Regression guard: `tests/unit/generic-quota-fetcher.test.ts`.
|
||||
- **fix(providers):** grok-cli (Grok Build) now strips `reasoning_effort`/`reasoning` before forwarding the request ([#6288](https://github.com/diegosouzapw/OmniRoute/issues/6288)) — Claude Code sends `reasoning_effort` on every request (routing the Opus slot), which Grok Build's upstream chat-proxy endpoint rejects with a 400; `transformRequest()`'s existing `UNSUPPORTED` sampling-param strip list (#5273) never covered it. Regression guard: `tests/unit/grok-cli-reasoning-strip-6288.test.ts`.
|
||||
- **fix(cli):** `omniroute serve` no longer hangs silently on a readiness timeout ([#6321](https://github.com/diegosouzapw/OmniRoute/issues/6321)) — the child server's stdout was piped to `"ignore"` whenever `--log`/`OMNIROUTE_SHOW_LOG` wasn't set (the default), discarding any debug output, and `runWithSupervisor`'s `waitForServer(...).then((up) => { if (up) {...} })` had no `else` branch, so a boot that never became ready produced zero further output after "⏳ Starting server...". Stdout is now buffered alongside stderr (`ServerSupervisor.getRecentLog()`), and a timeout prints a clear diagnostic plus the buffered output instead of staying silent. Does not by itself explain why boot never completes on a given machine — see the issue for further reproduction. Regression guard: `tests/unit/cli-serve-readiness-timeout-6321.test.ts`.
|
||||
|
||||
@@ -75,13 +75,24 @@ export class CloudflareAIExecutor extends BaseExecutor {
|
||||
// shape (`[{ type:"text", text }]`) with HTTP 400 (#2539). Flatten text parts to a string.
|
||||
if (!Array.isArray(body.messages)) return body;
|
||||
|
||||
// #6390: the endpoint has no way to carry image/non-text parts once flattened to a
|
||||
// string, so previously any non-text part (e.g. image_url) was silently mapped to ""
|
||||
// and the image quietly disappeared from the outgoing request. Refuse instead of
|
||||
// silently dropping data — this throws a plain Error which the caller (chatCore.ts)
|
||||
// already routes through buildErrorBody()/sanitizeErrorMessage() before it reaches
|
||||
// the client, matching the existing buildUrl() missing-accountId error above.
|
||||
const flattenContent = (content: unknown): unknown => {
|
||||
if (typeof content === "string" || !Array.isArray(content)) return content;
|
||||
return content
|
||||
.map((part) => {
|
||||
if (!part || typeof part !== "object") return "";
|
||||
const p = part as Record<string, unknown>;
|
||||
return p.type === "text" && typeof p.text === "string" ? p.text : "";
|
||||
if (p.type === "text" && typeof p.text === "string") return p.text;
|
||||
throw new Error(
|
||||
"Cloudflare Workers AI chat endpoint does not accept image/non-text content parts " +
|
||||
`(got type "${typeof p.type === "string" ? p.type : "unknown"}"). ` +
|
||||
"Remove image/file attachments or route this request to a vision-capable provider."
|
||||
);
|
||||
})
|
||||
.join("");
|
||||
};
|
||||
|
||||
53
tests/unit/cloudflare-ai-image-parts-6390.test.ts
Normal file
53
tests/unit/cloudflare-ai-image-parts-6390.test.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { CloudflareAIExecutor } from "../../open-sse/executors/cloudflare-ai.ts";
|
||||
|
||||
// Regression for #6390: the Workers AI /ai/v1/chat/completions endpoint only accepts a
|
||||
// plain-string `content` field. transformRequest() used to flatten every non-text content
|
||||
// part (e.g. image_url) to an empty string and silently join the rest — the image (or any
|
||||
// other non-text attachment) vanished from the outgoing request with no error surfaced to
|
||||
// the caller. transformRequest must now refuse the request instead of silently dropping data.
|
||||
test("CloudflareAIExecutor.transformRequest throws a clear error on image_url content parts (#6390)", () => {
|
||||
const executor = new CloudflareAIExecutor();
|
||||
const body = {
|
||||
model: "@cf/meta/llama-3.3-70b-instruct",
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "describe this image" },
|
||||
{ type: "image_url", image_url: { url: "https://example.com/cat.png" } },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
assert.throws(
|
||||
() => executor.transformRequest("@cf/meta/llama-3.3-70b-instruct", body, false, null),
|
||||
/does not accept image|non-text content/i
|
||||
);
|
||||
});
|
||||
|
||||
test("CloudflareAIExecutor.transformRequest still flattens plain text-part messages (#6390 no-regression)", () => {
|
||||
const executor = new CloudflareAIExecutor();
|
||||
const body = {
|
||||
model: "@cf/meta/llama-3.3-70b-instruct",
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "hello " },
|
||||
{ type: "text", text: "world" },
|
||||
],
|
||||
},
|
||||
{ role: "assistant", content: "plain stays plain" },
|
||||
],
|
||||
};
|
||||
|
||||
const out = executor.transformRequest("@cf/meta/llama-3.3-70b-instruct", body, false, null);
|
||||
const messages = out.messages as Array<{ content: unknown }>;
|
||||
|
||||
assert.equal(messages[0].content, "hello world");
|
||||
assert.equal(messages[1].content, "plain stays plain");
|
||||
});
|
||||
Reference in New Issue
Block a user