Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ _In development — bullets added per PR; finalized at release._
- **dashboard:** add a GitHub releases fallback to the "Update Available" lookup. After the v3.8.28 fix added an npm-registry HTTP fallback, the banner could still stay hidden on networks that reach GitHub (where the news feed already loads) but not `registry.npmjs.org`. `resolveLatestVersion()` now tries npm CLI → npm registry → GitHub releases (`/repos/diegosouzapw/OmniRoute/releases/latest`) before giving up, and logs a warning only when all three fail (#4100)
- **command-code:** omit `max_tokens` when the client omits it so the upstream applies the model's native default, fixing `400 "expected <=200000"` on `/alpha/generate` for high-cap models; an explicit oversized client value is clamped to the 200k endpoint ceiling (#5221 — thanks @adivekar-utexas)
- **combo:** wire session stickiness into the round-robin dispatch path. Multi-turn conversations from clients that send no session id (Codex CLI, Claude Code, most OpenAI-compatible tools) were rotated to a different connection on every turn by round-robin combos, busting the upstream prompt-cache → cold high-reasoning starts, intermittent `504`s and throughput collapse under concurrency. The weighted/priority paths already honored per-conversation stickiness; the round-robin handler returned before reaching it. Round-robin now starts the rotation at the conversation's sticky connection (failover to the other targets is preserved), and different conversations still spread across connections — only intra-conversation rotation is removed ([#5248](https://github.com/diegosouzapw/OmniRoute/pull/5248), #3825 — thanks @bypanghu, @jpsn123, @xz-dev)
- **kiro:** replace the synthesized trailing `"Continue"` turn with a neutral filler (`"..."`) — when an OpenAI→Kiro request ends on an assistant/tool turn, the translator synthesizes the protocol-required trailing user turn, and the literal word `"Continue"` could be read by Kiro/CodeWhisperer as a real user instruction and trigger unintended agent action. A trailing tool-result turn is still promoted as-is (it already collapses to a real user turn); only the assistant-text-ending case is affected. Regression guards: `tests/unit/kiro-continue-filler-5231.test.ts`. ([#5231](https://github.com/diegosouzapw/OmniRoute/issues/5231))
- **combo:** advance to the next combo target on a `400 "requested model is not supported"` instead of hard-failing. The 400 guard in the priority strategy treated `MODEL_CAPACITY` as a block-fallback reason, so a combo that hit a provider lacking a specific model returned a hard `400` even when other targets (different providers) supported it. Such 400s now fall through to the next target. ([#5249](https://github.com/diegosouzapw/OmniRoute/pull/5249) — thanks @Chewji9875)
- **dashboard:** disabled no-auth providers no longer vanish from the All Providers page. Disabling a no-auth provider (the "No authentication required" toggle, which adds it to `blockedProviders`) silently removed its card because the page _dropped_ blocked no-auth entries from its render list — the only way back was buried under Settings → Security → Blocked Providers. The page now **partitions** no-auth entries: visible providers render as before, blocked ones appear in a "Disabled" sub-group with an **Enable** button that un-blocks them in place. Aggregates, counts and `/v1/models` still consume the visible-only list (blocked providers stay out of routing). Regression guard: `tests/unit/noauth-blocked-partition-5183.test.ts`. ([#5183](https://github.com/diegosouzapw/OmniRoute/issues/5183), follow-up from [#5166](https://github.com/diegosouzapw/OmniRoute/issues/5166) — thanks @WslzGmzs)
- **providers:** drop the retired GPT‑5.2 / GPT‑4.5 models from the direct **ChatGPT‑web** and **Codex** surfaces (OpenAI removed them there), so OmniRoute stops advertising/routing models that no longer exist. Scoped on purpose to those two providers — third‑party proxies that still expose the ids are untouched. ([#5280](https://github.com/diegosouzapw/OmniRoute/pull/5280) — thanks @backryun)
Expand Down
8 changes: 4 additions & 4 deletions open-sse/translator/request/openai-to-kiro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,14 +405,14 @@ function convertMessages(messages, tools, model) {

// Kiro requires currentMessage to be a user turn. If the request ends with a
// user turn, move that final turn into currentMessage. If it ends with an
// assistant/tool turn, keep chronological history intact and ask Kiro to
// continue instead of reordering prior turns.
// assistant/tool turn, synthesize a neutral filler ("...") instead of the
// literal "Continue", which Kiro can read as a real instruction (#5231).
if (history.length > 0 && history[history.length - 1].userInputMessage) {
currentMessage = history.pop();
} else {
currentMessage = {
userInputMessage: {
content: "Continue",
content: "...",
modelId: model,
},
};
Expand All @@ -435,7 +435,7 @@ function convertMessages(messages, tools, model) {

// Fallback: if the schema was never attached to any user turn (e.g. the
// input contained no user messages and currentMessage is a synthesized
// "Continue" turn), attach the provided tools directly to currentMessage so
// neutral-filler turn), attach the provided tools directly to currentMessage so
// Kiro still sees the schema it needs to validate assistant.toolUses in
// history.
if (
Expand Down
64 changes: 64 additions & 0 deletions tests/unit/kiro-continue-filler-5231.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import test from "node:test";
import assert from "node:assert/strict";

const { buildKiroPayload } = await import("../../open-sse/translator/request/openai-to-kiro.ts");

// Regression guards for #5231: when an OpenAI->Kiro request ends on an
// assistant/tool turn, the translator must synthesize a *neutral* filler user
// turn ("...") rather than the literal word "Continue" — Kiro/CodeWhisperer can
// read "Continue" as a real user instruction and take unintended agent action.

test("#5231: assistant-text-ending request never leaks the literal 'Continue' filler", () => {
const result = buildKiroPayload(
"claude-sonnet-4",
{
messages: [
{ role: "user", content: "First user" },
{ role: "assistant", content: "Assistant answer" },
],
},
false,
null
);

const synthesized = result.conversationState.currentMessage.userInputMessage.content;
assert.match(synthesized, /\n\n\.\.\.$/, "synthesized trailing turn must end with the neutral filler");
assert.ok(
!/\bContinue\b/.test(synthesized),
`synthesized trailing turn must not contain the literal "Continue", got: ${synthesized}`
);
});

test("#5231: a trailing tool-result turn is promoted as-is, NOT replaced by the filler", () => {
// Proves the change is scoped strictly to the assistant-text-ending case: a
// conversation ending on a tool result already collapses to a real user turn
// (carrying its toolResults), which is promoted into currentMessage unchanged.
const result = buildKiroPayload(
"claude-sonnet-4",
{
messages: [
{ role: "user", content: "Run the tool" },
{
role: "assistant",
content: null,
tool_calls: [
{ id: "call_1", type: "function", function: { name: "get_time", arguments: "{}" } },
],
},
{ role: "tool", tool_call_id: "call_1", content: "12:00" },
],
},
false,
null
);

const current = result.conversationState.currentMessage.userInputMessage;
assert.ok(
!/\.\.\.$/.test(current.content) && !/\bContinue\b/.test(current.content),
`trailing tool-result turn must be promoted as-is, got synthesized filler: ${current.content}`
);
assert.ok(
(current.userInputMessageContext?.toolResults?.length ?? 0) > 0,
"promoted trailing turn must carry the tool results"
);
});
4 changes: 2 additions & 2 deletions tests/unit/translator-openai-to-kiro.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ test("OpenAI -> Kiro maps invalid or empty assistant tool call arguments to empt
);
});

test("OpenAI -> Kiro uses Continue currentMessage when the request ends with assistant history", () => {
test("OpenAI -> Kiro uses a neutral filler currentMessage when the request ends with assistant history (#5231)", () => {
const result = buildKiroPayload(
"claude-sonnet-4",
{
Expand All @@ -217,7 +217,7 @@ test("OpenAI -> Kiro uses Continue currentMessage when the request ends with ass

assert.match(
result.conversationState.currentMessage.userInputMessage.content,
/^\[Context: Current time is .*Z\]\n\nContinue$/
/^\[Context: Current time is .*Z\]\n\n\.\.\.$/
);
assert.deepEqual(result.conversationState.history, [
{
Expand Down
Loading