mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-13 18:52:18 +03:00
* fix(executors): route Claude-via-Vertex through native rawPredict with real streaming Claude models on Vertex AI were being sent through the generic OpenAI- compatible partner endpoint, which 404s/errors for Claude on at least some projects. Route them through Vertex's native Anthropic Messages API (publishers/anthropic/.../rawPredict) instead, stripping the body-level model field rawPredict rejects and injecting the required anthropic_version field. rawPredict only ever returns a complete JSON body, never real SSE framing, so streaming requests now get a genuine Anthropic-format SSE stream synthesized from that JSON (message_start/content_block_*/ message_delta/message_stop), which the existing claude-to-openai response translator already knows how to parse. Also fixes two response-format resolution bugs that silently dropped a custom model's DB-stored targetFormat override whenever the model id also existed in the static provider registry (as claude-sonnet-4-6 and claude-opus-4-7 do under vertex): resolveModelOrError had its own ad-hoc resolution that never consulted the override, and even once fixed, executeChatWithBreaker discarded the correctly-resolved format before handleChatCore's own resolution ran a second time. * docs: add changelog fragment for #8909 * refactor(sse): extract shared Claude effort-model predicate * fix(sse): strip Claude effort-suffix ids for any provider serving a real Claude model * fix(sse): keep no-think and CC-discovery catalog variant roots unprefixed * fix(dashboard): re-qualify no-think playground model ids correctly * fix(sse): scope Vertex 404s to a per-model lockout via passthroughModels * docs: add changelog fragment for the Claude catalog/dispatch fix * fix(sse): align regex naming and changelog formatting * fix(sse): clarify effort-variant strip comment and add cross-module drift guard * fix(sse): disambiguate Vertex connection-wide vs per-model 403s * docs: document Vertex 403 disambiguation in changelog fragment * fix(sse): correlate reason and resource within the same ErrorInfo detail * fix(sse): extract Vertex error classifier and rebaseline frozen file sizes * test: register vertex-passthrough-model-lockout in stryker tap.testFiles * fix(sse): reconciles rebase-onto-tip drift for 9006 Two categories of inherited base-branch breakage surfaced when rebasing onto release/v3.8.50's latest tip, both confirmed unrelated to this PR's own diff: - check:file-size: base.ts and chat.ts drifted further past their frozen caps via already-merged commits (7163081f5and others) that didn't rebaseline after growing them. Documented and bumped in file-size-baseline.json. - chat-helpers.test.ts: two gpt-5.5 routing assertions predate #9275 (fix(routing): bare model ids route to codex first), which deliberately made gpt-5.5 route to codex unconditionally, regardless of which other providers are active. Confirmed via #9275's own commit message and code comments this is intentional, not a regression; verified reproducible on the raw base tip alone, with no changes from this PR involved. Updated both assertions and their names to match the new, intentional default. * ci: re-trigger checks after GitHub Actions incident (2026-08-07, resolved) * ci: re-trigger checks (previous push event was dropped) * fix(quality): rebaseline combo-routing-engine.test.ts own-comment growth The ALL_ACCOUNTS_INACTIVE->ALL_TARGETS_SKIPPED fix (a32aed738) added explanatory comments (+7 lines), pushing the file past its frozen 3457 cap. CI's PR-mode check:file-size caught it; local check-file-size.mjs was not re-run after that specific commit.
74 lines
3.4 KiB
TypeScript
74 lines
3.4 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
const { qualifyPlaygroundModel } =
|
|
await import("../../src/app/(dashboard)/dashboard/media-providers/components/LlmChatCard.tsx");
|
|
|
|
// #3050 — vendor-namespaced model ids already contain a "/", so the old
|
|
// `.includes("/")` heuristic skipped the provider prefix and the request was
|
|
// rejected with "Ambiguous model 'moonshotai/kimi-k2.6'".
|
|
test("qualifyPlaygroundModel prefixes a vendor-namespaced model with providerId (#3050)", () => {
|
|
assert.equal(qualifyPlaygroundModel("moonshotai/kimi-k2.6", "nim"), "nim/moonshotai/kimi-k2.6");
|
|
assert.equal(
|
|
qualifyPlaygroundModel("nvidia/zyphra/zamba2-7b-instruct", "nim"),
|
|
"nim/nvidia/zyphra/zamba2-7b-instruct"
|
|
);
|
|
});
|
|
|
|
test("qualifyPlaygroundModel prefixes a bare model", () => {
|
|
assert.equal(qualifyPlaygroundModel("gpt-4o", "openai"), "openai/gpt-4o");
|
|
});
|
|
|
|
test("qualifyPlaygroundModel does not double-prefix an already-qualified model", () => {
|
|
assert.equal(
|
|
qualifyPlaygroundModel("nim/moonshotai/kimi-k2.6", "nim"),
|
|
"nim/moonshotai/kimi-k2.6"
|
|
);
|
|
assert.equal(qualifyPlaygroundModel("nim", "nim"), "nim");
|
|
});
|
|
|
|
test("qualifyPlaygroundModel returns the model unchanged without a providerId", () => {
|
|
assert.equal(qualifyPlaygroundModel("moonshotai/kimi-k2.6", ""), "moonshotai/kimi-k2.6");
|
|
assert.equal(qualifyPlaygroundModel("", "nim"), "");
|
|
});
|
|
|
|
test("OpenCode Free playground uses its routing alias instead of the reserved provider id", async () => {
|
|
const { getProviderAlias } = await import("../../src/shared/constants/providers.ts");
|
|
assert.equal(getProviderAlias("opencode"), "oc");
|
|
assert.equal(qualifyPlaygroundModel("big-pickle", getProviderAlias("opencode")), "oc/big-pickle");
|
|
});
|
|
|
|
test("qualifyPlaygroundModel inserts the provider after the no-think prefix, not before it", () => {
|
|
assert.equal(
|
|
qualifyPlaygroundModel("no-think/claude-sonnet-5", "vertex"),
|
|
"no-think/vertex/claude-sonnet-5"
|
|
);
|
|
});
|
|
|
|
test("qualifyPlaygroundModel does not double-qualify an already-qualified no-think id", () => {
|
|
assert.equal(
|
|
qualifyPlaygroundModel("no-think/vertex/claude-sonnet-5", "vertex"),
|
|
"no-think/vertex/claude-sonnet-5"
|
|
);
|
|
});
|
|
|
|
test("qualifyPlaygroundModel does not mistake a provider-name-prefix collision for already-qualified", () => {
|
|
// routingPrefix "vertex" must not match "vertex-eu/..." as already-qualified just because
|
|
// it starts with the same characters — the check requires an exact "vertex/" segment
|
|
// boundary. A naive `inner.startsWith(routingPrefix)` (no slash) would wrongly skip
|
|
// qualification here and leave the provider segment un-inserted.
|
|
assert.equal(
|
|
qualifyPlaygroundModel("no-think/vertex-eu/claude-sonnet-5", "vertex"),
|
|
"no-think/vertex/vertex-eu/claude-sonnet-5"
|
|
);
|
|
});
|
|
|
|
test("LlmChatCard's local NO_THINKING_PREFIX literal matches the canonical constant", async () => {
|
|
// Drift guard: LlmChatCard.tsx deliberately hardcodes "no-think/" as a literal instead
|
|
// of importing NO_THINKING_PREFIX from open-sse/utils/noThinkingAlias.ts (avoids pulling
|
|
// server-side catalog modules into the client bundle — see Step 1). This test file is not
|
|
// client-bundled, so it can safely import the real constant and assert they never drift.
|
|
const { NO_THINKING_PREFIX } = await import("../../open-sse/utils/noThinkingAlias.ts");
|
|
assert.equal(NO_THINKING_PREFIX, "no-think/");
|
|
});
|