mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-24 16:12:23 +03:00
fix(providers): eliminate Gemini 3.5 Flash
This commit is contained in:
@@ -1 +0,0 @@
|
||||
- fix(sse): mark gemini-3.5-flash as thinking-capable so reasoning_effort is no longer rejected with a spurious 400 (#10286)
|
||||
@@ -6,7 +6,7 @@ lastUpdated: 2026-07-31
|
||||
|
||||
# OmniRoute Antigravity (Google One AI) Onboarding Guide
|
||||
|
||||
> **What you get**: Access to Gemini 3.1 Pro, Gemini 3.5 Flash, Claude Sonnet 4.6, and other models through your Google One AI Pro subscription — routed through OmniRoute as a unified gateway.
|
||||
> **What you get**: Access to Gemini 3.1 Pro, Gemini 3.7 Flash, Claude Sonnet 4.6, and other models through your Google One AI Pro subscription — routed through OmniRoute as a unified gateway.
|
||||
|
||||
**Official references**:
|
||||
|
||||
@@ -45,7 +45,7 @@ Both providers share the **same Google backend** — identical OAuth client, tok
|
||||
|
||||
**Why the model catalog differs**: Google's CLI is "optimized for speed and low overhead" and "co-optimized with Gemini models" (per Google's official blog). The Web/IDE product is "optimized for comprehensiveness." The CLI uses `:fetchAvailableModels` to dynamically discover models, while the IDE uses a static curated list.
|
||||
|
||||
**In practice**: Use `agy/` prefix for Gemini models (e.g. `agy/gemini-3.5-flash-high`). Use `antigravity/` for the static curated list. Both hit the same Google backend, but expose different model naming. The quota is shared — using either provider counts against the same Google account's limits.
|
||||
**In practice**: Use `agy/` prefix for Gemini models (e.g. `agy/gemini-3.7-flash-high`). Use `antigravity/` for the static curated list. Both hit the same Google backend, but expose different model naming. The quota is shared — using either provider counts against the same Google account's limits.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@ const AGY_RETIRED_MODEL_IDS = new Set([
|
||||
"gemini-3.6-flash-medium",
|
||||
"gemini-3.6-flash-low",
|
||||
"gemini-3-flash-agent",
|
||||
"gemini-3.5-flash",
|
||||
"gemini-3.5-flash-extra-low",
|
||||
"gemini-3.5-flash-low",
|
||||
"gemini-3.5-flash-high",
|
||||
|
||||
@@ -179,6 +179,7 @@ const ANTIGRAVITY_RETIRED_MODEL_IDS = new Set([
|
||||
"gemini-3.6-flash-medium",
|
||||
"gemini-3.6-flash-low",
|
||||
"gemini-3-flash-agent",
|
||||
"gemini-3.5-flash",
|
||||
"gemini-3.5-flash-extra-low",
|
||||
"gemini-3.5-flash-low",
|
||||
"gemini-3.5-flash-high",
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"gemma-4-26b-it": { "rpm": 16000, "rpd": 14400, "tpm": 16000 },
|
||||
"gemma-4-31b-it": { "rpm": 16000, "rpd": 14400, "tpm": 16000 },
|
||||
"gemini-embedding-exp-03-07": { "rpm": 100, "rpd": 1000, "tpm": 30000 },
|
||||
"gemini-3.5-flash": { "rpm": 5, "rpd": 20, "tpm": 250000 },
|
||||
"gemini-3.1-flash-lite": { "rpm": 15, "rpd": 500, "tpm": 250000 },
|
||||
"gemini-3.1-pro": { "rpm": 0, "rpd": 0, "tpm": 0 },
|
||||
"gemini-2.5-flash-lite": { "rpm": 10, "rpd": 20, "tpm": 250000 },
|
||||
|
||||
@@ -228,14 +228,14 @@ export const cursorProvider: RegistryEntry = {
|
||||
{ id: "gpt-5.1-low", name: "GPT-5.1 Low" },
|
||||
{ id: "gpt-5.1", name: "GPT-5.1" },
|
||||
{ id: "gpt-5.1-high", name: "GPT-5.1 High" },
|
||||
{ id: "gemini-3.5-flash", name: "Gemini 3.5 Flash" },
|
||||
{ id: "claude-4-sonnet", name: "Sonnet 4" },
|
||||
{ id: "claude-4-sonnet-thinking", name: "Sonnet 4 Thinking" },
|
||||
{ id: "gpt-5-mini", name: "GPT-5 Mini" },
|
||||
{ id: "kimi-k3-low", name: "Kimi K3 Low" },
|
||||
{ id: "kimi-k3-max", name: "Kimi K3" },
|
||||
{ id: "glm-5.2-high", name: "GLM 5.2" },
|
||||
{ id: "glm-5.2-max", name: "GLM 5.2 Max" }, ],
|
||||
{ id: "glm-5.2-max", name: "GLM 5.2 Max" },
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -78,7 +78,7 @@ const FALLBACK_MODEL_SEEDS: FallbackModelSeed[] = [
|
||||
|
||||
/** Presets exposed by the web client's model picker (id → text/multimodal model). */
|
||||
export const CONOL_FALLBACK_MODEL_PRESETS: ConolModelPreset[] = [
|
||||
{ id: "flash", text: "deepseek/deepseek-v4-flash", multimodal: "google/gemini-3.5-flash" },
|
||||
{ id: "flash", text: "deepseek/deepseek-v4-flash", multimodal: "google/gemini-3.7-flash" },
|
||||
{ id: "moderate", text: "deepseek/deepseek-v4-pro", multimodal: "claude-sonnet-5" },
|
||||
{ id: "pro", text: "z-ai/glm-5.2", multimodal: "moonshotai/kimi-k3" },
|
||||
{ id: "ultra", text: "claude-fable-5", multimodal: "claude-fable-5" },
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
export interface PromptQlModel {
|
||||
/** Client-facing id (model_reference slug, e.g. gemini-3.5-flash). */
|
||||
/** Client-facing id (model_reference slug, e.g. gemini-3.7-flash). */
|
||||
id: string;
|
||||
/** Friendly picker label. */
|
||||
name: string;
|
||||
|
||||
@@ -34,6 +34,8 @@ const IGNORED_METHODS = new Set([
|
||||
"asyncBatchEmbedContent",
|
||||
]);
|
||||
|
||||
const RETIRED_GEMINI_MODEL_IDS = new Set(["gemini-3.5-flash"]);
|
||||
|
||||
export interface GeminiDiscoveryModel {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -46,36 +48,38 @@ export interface GeminiDiscoveryModel {
|
||||
}
|
||||
|
||||
export function parseGeminiModelsList(data: any): GeminiDiscoveryModel[] {
|
||||
return (data?.models || []).map((m: Record<string, unknown>) => {
|
||||
const methods: string[] = Array.isArray(m.supportedGenerationMethods)
|
||||
? (m.supportedGenerationMethods as string[])
|
||||
: [];
|
||||
return (data?.models || [])
|
||||
.map((m: Record<string, unknown>) => {
|
||||
const methods: string[] = Array.isArray(m.supportedGenerationMethods)
|
||||
? (m.supportedGenerationMethods as string[])
|
||||
: [];
|
||||
|
||||
const endpoints = new Set<string>(
|
||||
methods
|
||||
.filter((method) => !IGNORED_METHODS.has(method))
|
||||
.map((method) => METHOD_TO_ENDPOINT[method] || "chat")
|
||||
);
|
||||
const endpoints = new Set<string>(
|
||||
methods
|
||||
.filter((method) => !IGNORED_METHODS.has(method))
|
||||
.map((method) => METHOD_TO_ENDPOINT[method] || "chat")
|
||||
);
|
||||
|
||||
const id = ((m.name as string) || (m.id as string) || "").replace(/^models\//, "");
|
||||
const lowerId = id.toLowerCase();
|
||||
const id = ((m.name as string) || (m.id as string) || "").replace(/^models\//, "");
|
||||
const lowerId = id.toLowerCase();
|
||||
|
||||
// Keep Veo models in the video bucket even when the method list is incomplete.
|
||||
if (lowerId.includes("veo")) {
|
||||
endpoints.add("video");
|
||||
}
|
||||
// Keep Veo models in the video bucket even when the method list is incomplete.
|
||||
if (lowerId.includes("veo")) {
|
||||
endpoints.add("video");
|
||||
}
|
||||
|
||||
if (endpoints.size === 0) endpoints.add("chat");
|
||||
if (endpoints.size === 0) endpoints.add("chat");
|
||||
|
||||
return {
|
||||
...m,
|
||||
id,
|
||||
name: (m.displayName as string) || id,
|
||||
supportedEndpoints: [...endpoints],
|
||||
...(typeof m.inputTokenLimit === "number" ? { inputTokenLimit: m.inputTokenLimit } : {}),
|
||||
...(typeof m.outputTokenLimit === "number" ? { outputTokenLimit: m.outputTokenLimit } : {}),
|
||||
...(typeof m.description === "string" ? { description: m.description } : {}),
|
||||
...(m.thinking === true ? { supportsThinking: true } : {}),
|
||||
} as GeminiDiscoveryModel;
|
||||
});
|
||||
return {
|
||||
...m,
|
||||
id,
|
||||
name: (m.displayName as string) || id,
|
||||
supportedEndpoints: [...endpoints],
|
||||
...(typeof m.inputTokenLimit === "number" ? { inputTokenLimit: m.inputTokenLimit } : {}),
|
||||
...(typeof m.outputTokenLimit === "number" ? { outputTokenLimit: m.outputTokenLimit } : {}),
|
||||
...(typeof m.description === "string" ? { description: m.description } : {}),
|
||||
...(m.thinking === true ? { supportsThinking: true } : {}),
|
||||
} as GeminiDiscoveryModel;
|
||||
})
|
||||
.filter((model: GeminiDiscoveryModel) => !RETIRED_GEMINI_MODEL_IDS.has(model.id));
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ import { normalizeDiscoveredModels } from "@/lib/providerModels/modelDiscovery";
|
||||
import {
|
||||
ANTIGRAVITY_MODEL_ALIASES,
|
||||
ANTIGRAVITY_REVERSE_MODEL_ALIASES,
|
||||
isDiscoverableAntigravityModelId,
|
||||
} from "@omniroute/open-sse/config/antigravityModelAliases.ts";
|
||||
import { isDiscoverableAgyModelId } from "@omniroute/open-sse/config/agyModels.ts";
|
||||
import { filterChatSelectableModels } from "@omniroute/open-sse/services/modelEndpointPolicy.ts";
|
||||
import { filterSelectableModels } from "@omniroute/open-sse/services/modelLifecycle.ts";
|
||||
import { isSelfHostedChatProvider } from "@/shared/constants/providers";
|
||||
@@ -254,14 +256,22 @@ export async function importManagedModels({
|
||||
const previousSyncedAvailableModels =
|
||||
previousSyncedAvailableModelsInput ??
|
||||
(await getSyncedAvailableModelsForConnection(providerId, connectionId));
|
||||
const normalizedDiscoveredModels = normalizeDiscoveredModels(fetchedModels, providerId);
|
||||
// Gemini 3.5 Flash elimination (ddf1bb760, carried from #11259): antigravity/
|
||||
// agy discovery is restricted to each family's discoverable ids BEFORE any
|
||||
// chat-selection filtering.
|
||||
const providerFilteredModels =
|
||||
providerId === "antigravity"
|
||||
? normalizedDiscoveredModels.filter((model) => isDiscoverableAntigravityModelId(model.id))
|
||||
: providerId === "agy"
|
||||
? normalizedDiscoveredModels.filter((model) => isDiscoverableAgyModelId(model.id))
|
||||
: normalizedDiscoveredModels;
|
||||
// #11088 (option 1): self-hosted providers keep their non-chat models — chat
|
||||
// filtering happens at read time (resolveLocalSyncedEndpointRoute). Every other
|
||||
// provider keeps the import-time chat filter: the read-time path is gated on
|
||||
// isSelfHostedChatProvider, so dropping it globally leaked image/video models
|
||||
// into OpenAI chat selections (#11271).
|
||||
const selectableModels = filterSelectableModels(
|
||||
providerId,
|
||||
normalizeDiscoveredModels(fetchedModels, providerId)
|
||||
const selectableModels = filterSelectableModels(providerId, providerFilteredModels);
|
||||
);
|
||||
const discoveredModels = isSelfHostedChatProvider(providerId)
|
||||
? selectableModels
|
||||
|
||||
@@ -99,7 +99,7 @@ const GPT_5_6_MODEL_SPEC = {
|
||||
supportsVision: true,
|
||||
} satisfies ModelSpec;
|
||||
|
||||
const GEMINI_35_FLASH_MODEL_SPEC = {
|
||||
const GEMINI_36_FLASH_MODEL_SPEC = {
|
||||
maxOutputTokens: 65536,
|
||||
contextWindow: 1048576,
|
||||
supportsThinking: false,
|
||||
@@ -160,7 +160,7 @@ export const MODEL_SPECS: Record<string, ModelSpec> = {
|
||||
aliases: ["openai/gpt-4o"],
|
||||
},
|
||||
|
||||
// ── Gemini 2.5 and provider-neutral 3.5 Flash series ─────────────
|
||||
// ── Gemini 2.5 Flash ─────────────────────────────────────────────
|
||||
"gemini-2.5-flash": {
|
||||
maxOutputTokens: 65536,
|
||||
contextWindow: 1048576,
|
||||
@@ -171,16 +171,6 @@ export const MODEL_SPECS: Record<string, ModelSpec> = {
|
||||
supportsTools: true,
|
||||
supportsVision: true,
|
||||
},
|
||||
"gemini-3.5-flash-extra-low": {
|
||||
...GEMINI_35_FLASH_MODEL_SPEC,
|
||||
thinkingBudgetCap: 0,
|
||||
},
|
||||
"gemini-3.5-flash-low": { ...GEMINI_35_FLASH_MODEL_SPEC },
|
||||
"gemini-3-flash-agent": {
|
||||
...GEMINI_35_FLASH_MODEL_SPEC,
|
||||
thinkingBudgetCap: 0,
|
||||
},
|
||||
|
||||
// ── Gemini 3.7 Flash (current Antigravity/AGY live tiers) ─────────
|
||||
// The tier suffix configures the thinking budget passed to the upstream
|
||||
// gemini-3.7-flash-tiered backend (high: 24.5k, medium: 8k, low: 1k).
|
||||
@@ -234,9 +224,9 @@ export const MODEL_SPECS: Record<string, ModelSpec> = {
|
||||
// Provider-neutral compatibility for providers that still serve Gemini 3.6.
|
||||
// Antigravity/AGY availability is governed by their own provider catalogs and
|
||||
// retirement filters; these shared specs must not be treated as an allowlist.
|
||||
"gemini-3.6-flash-high": { ...GEMINI_35_FLASH_MODEL_SPEC },
|
||||
"gemini-3.6-flash-medium": { ...GEMINI_35_FLASH_MODEL_SPEC },
|
||||
"gemini-3.6-flash-low": { ...GEMINI_35_FLASH_MODEL_SPEC },
|
||||
"gemini-3.6-flash-high": { ...GEMINI_36_FLASH_MODEL_SPEC },
|
||||
"gemini-3.6-flash-medium": { ...GEMINI_36_FLASH_MODEL_SPEC },
|
||||
"gemini-3.6-flash-low": { ...GEMINI_36_FLASH_MODEL_SPEC },
|
||||
|
||||
// ── Gemini 3 Flash series ───────────────────────────────────────
|
||||
"gemini-3-flash": {
|
||||
@@ -282,20 +272,6 @@ export const MODEL_SPECS: Record<string, ModelSpec> = {
|
||||
aliases: ["gemini-3-pro-low"],
|
||||
},
|
||||
|
||||
// ── Gemini 3.5 Flash ─────────────────────────────────────────────
|
||||
// #10286: the base Google AI Studio model DOES support reasoning (it has
|
||||
// an effort-tier alias gemini-3.5-flash-high) — override the shared spec's
|
||||
// supportsThinking:false here only. Do NOT flip GEMINI_35_FLASH_MODEL_SPEC
|
||||
// itself: it is also spread into the Antigravity flash-tier aliases
|
||||
// (gemini-3.5-flash-low/-extra-low, gemini-3-flash-agent, gemini-3.6-flash-*)
|
||||
// which reject client-supplied thinking params because the model id itself
|
||||
// selects the reasoning tier upstream.
|
||||
"gemini-3.5-flash": {
|
||||
...GEMINI_35_FLASH_MODEL_SPEC,
|
||||
supportsThinking: true,
|
||||
aliases: ["gemini-3.5-flash-high"],
|
||||
},
|
||||
|
||||
// ── Claude Opus 4.5 ─────────────────────────────────────────────
|
||||
"claude-opus-4-5": {
|
||||
maxOutputTokens: 32768,
|
||||
|
||||
@@ -57,6 +57,7 @@ test("agy ships its own live callable model catalog", () => {
|
||||
assert.ok(!ids.includes("gemini-3.6-flash-low"));
|
||||
assert.ok(!ids.includes("gemini-3.6-flash-medium"));
|
||||
assert.ok(!ids.includes("gemini-3.6-flash-high"));
|
||||
assert.ok(!ids.includes("gemini-3.5-flash"));
|
||||
assert.ok(!ids.includes("gemini-3.5-flash-extra-low"));
|
||||
assert.ok(!ids.includes("gemini-3.5-flash-low"));
|
||||
assert.ok(!ids.includes("gemini-3-flash-agent"));
|
||||
@@ -87,6 +88,7 @@ test("agy model helpers resolve catalog ids and display names", () => {
|
||||
assert.equal(isUserCallableAgyModelId("gemini-3.6-flash-low"), false);
|
||||
assert.equal(isUserCallableAgyModelId("gemini-3.6-flash-medium"), false);
|
||||
assert.equal(isUserCallableAgyModelId("gemini-3.6-flash-high"), false);
|
||||
assert.equal(isUserCallableAgyModelId("gemini-3.5-flash"), false);
|
||||
assert.equal(isUserCallableAgyModelId("gemini-3.5-flash-extra-low"), false);
|
||||
assert.equal(isUserCallableAgyModelId("gemini-3.5-flash-low"), false);
|
||||
assert.equal(isUserCallableAgyModelId("gemini-3-flash-agent"), false);
|
||||
|
||||
@@ -74,7 +74,7 @@ test("TDD S3: checkFallbackError extracts retry hint for oauth providers even if
|
||||
429,
|
||||
errorText,
|
||||
0,
|
||||
"gemini-3.5-flash",
|
||||
"gemini-3.7-flash",
|
||||
"antigravity", // which uses oauth provider profile (useUpstreamRetryHints: false)
|
||||
null
|
||||
);
|
||||
|
||||
@@ -31,6 +31,7 @@ const RETIRED_FLASH_IDS = [
|
||||
"gemini-3.6-flash-low",
|
||||
"gemini-3.6-flash-medium",
|
||||
"gemini-3.6-flash-high",
|
||||
"gemini-3.5-flash",
|
||||
"gemini-3.5-flash-extra-low",
|
||||
"gemini-3.5-flash-low",
|
||||
"gemini-3-flash-agent",
|
||||
|
||||
@@ -21,6 +21,7 @@ const RETIRED_PUBLIC_MODELS = [
|
||||
"gemini-3.6-flash-medium",
|
||||
"gemini-3.6-flash-low",
|
||||
"gemini-3-flash-agent",
|
||||
"gemini-3.5-flash",
|
||||
"gemini-3.5-flash-low",
|
||||
"gemini-3.5-flash-extra-low",
|
||||
"gemini-2.5-pro",
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from "../../open-sse/executors/conol-web.ts";
|
||||
import {
|
||||
CONOL_FALLBACK_MODELS,
|
||||
CONOL_FALLBACK_MODEL_PRESETS,
|
||||
clampConolEffort,
|
||||
parseConolAgentServers,
|
||||
resolveConolModelSelection,
|
||||
@@ -27,6 +28,11 @@ import { getResolvedModelCapabilities } from "../../src/lib/modelCapabilities.ts
|
||||
const SESSION_COOKIE_NAME = "__Secure-better-auth.session_token";
|
||||
|
||||
describe("Conol web provider", () => {
|
||||
it("routes the Flash preset multimodal path to Gemini 3.7", () => {
|
||||
const flashPreset = CONOL_FALLBACK_MODEL_PRESETS.find((preset) => preset.id === "flash");
|
||||
assert.equal(flashPreset?.multimodal, "google/gemini-3.7-flash");
|
||||
});
|
||||
|
||||
it("normalizes raw, full-header, JSON, and provider-data credentials", () => {
|
||||
assert.equal(normalizeConolCookie("token-value"), `${SESSION_COOKIE_NAME}=token-value`);
|
||||
assert.equal(
|
||||
|
||||
@@ -55,7 +55,7 @@ describe("GithubExecutor — Gemini/Claude must never hit /responses (port 9rout
|
||||
|
||||
it("routes registered Gemini Copilot models to chat/completions", () => {
|
||||
const exec = new GithubExecutor();
|
||||
for (const id of ["gemini-3.1-pro-preview", "gemini-3.5-flash"]) {
|
||||
for (const id of ["gemini-3.1-pro-preview", "gemini-3.7-flash"]) {
|
||||
assert.equal(exec.buildUrl(id, false), CHAT_URL, `${id} must route to chat/completions`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,6 +8,10 @@ function modelIds(): Set<string> {
|
||||
return new Set(cursorProvider.models.map((m) => m.id));
|
||||
}
|
||||
|
||||
test("cursor registry excludes retired Gemini 3.5 Flash", () => {
|
||||
assert.equal(modelIds().has("gemini-3.5-flash"), false);
|
||||
});
|
||||
|
||||
test("cursor registry includes Claude Opus 4.8 effort + thinking + fast variants", () => {
|
||||
const ids = modelIds();
|
||||
for (const effort of EFFORTS) {
|
||||
|
||||
@@ -56,7 +56,7 @@ describe("PromptQl — registry consistency", () => {
|
||||
it("registers a model catalog via getModelsByProviderId", () => {
|
||||
const catalog = getModelsByProviderId("promptql");
|
||||
assert.ok(catalog.length >= 5);
|
||||
assert.ok(catalog.some((m) => m.id === "gemini-3.5-flash" || m.id.includes("gemini")));
|
||||
assert.ok(catalog.some((m) => m.id === "gemini-3.7-flash" || m.id.includes("gemini")));
|
||||
assert.ok(catalog.some((m) => m.id.includes("gpt-5.6") || m.id.includes("fable")));
|
||||
});
|
||||
});
|
||||
@@ -209,7 +209,10 @@ describe("PromptQl — helpers", () => {
|
||||
});
|
||||
|
||||
it("resolves model slugs and prefixes", () => {
|
||||
assert.equal(models.clientFacingPromptQlModelId("promptql/gemini-3.5-flash"), "gemini-3.5-flash");
|
||||
assert.equal(
|
||||
models.clientFacingPromptQlModelId("promptql/gemini-3.7-flash"),
|
||||
"gemini-3.7-flash"
|
||||
);
|
||||
assert.equal(models.clientFacingPromptQlModelId("pql/gpt-5.6-sol"), "gpt-5.6-sol");
|
||||
const r = models.resolvePromptQlModel("Claude Fable 5");
|
||||
assert.ok(r);
|
||||
@@ -265,7 +268,7 @@ describe("PromptQlExecutor — auth / validation", () => {
|
||||
it("returns 401 when no token is supplied", async () => {
|
||||
const executor = new mod.PromptQlExecutor();
|
||||
const result = await executor.execute({
|
||||
model: "gemini-3.5-flash",
|
||||
model: "gemini-3.7-flash",
|
||||
body: { messages: [{ role: "user", content: "hi" }] },
|
||||
stream: false,
|
||||
credentials: {},
|
||||
@@ -279,7 +282,7 @@ describe("PromptQlExecutor — auth / validation", () => {
|
||||
it("returns 400 when no user message is present", async () => {
|
||||
const executor = new mod.PromptQlExecutor();
|
||||
const result = await executor.execute({
|
||||
model: "gemini-3.5-flash",
|
||||
model: "gemini-3.7-flash",
|
||||
body: { messages: [{ role: "assistant", content: "hi" }] },
|
||||
stream: false,
|
||||
credentials: { apiKey: sampleJwt },
|
||||
@@ -614,7 +617,7 @@ describe("PromptQlExecutor — mocked GraphQL turn", () => {
|
||||
try {
|
||||
const executor = new mod.PromptQlExecutor();
|
||||
const result = await executor.execute({
|
||||
model: "gemini-3.5-flash",
|
||||
model: "gemini-3.7-flash",
|
||||
body: { messages: [{ role: "user", content: "ping" }] },
|
||||
stream: false,
|
||||
credentials: { apiKey: sampleJwt },
|
||||
@@ -628,7 +631,7 @@ describe("PromptQlExecutor — mocked GraphQL turn", () => {
|
||||
};
|
||||
assert.equal(json.choices[0]!.message.content, "HELLO-PQL");
|
||||
assert.equal(json.promptql_thread_id, "thread-1");
|
||||
assert.equal(json.model, "gemini-3.5-flash");
|
||||
assert.equal(json.model, "gemini-3.7-flash");
|
||||
assert.ok(call >= 2);
|
||||
assert.equal(result.response.headers.get("X-PromptQL-Thread-Id"), "thread-1");
|
||||
} finally {
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
// Regression test for #10286: gemini-3.5-flash was incorrectly marked
|
||||
// supportsThinking:false, causing a spurious pre-provider HTTP 400 for any
|
||||
// request with reasoning_effort set, even though the base Google AI Studio
|
||||
// model supports reasoning (it has an effort-tier alias gemini-3.5-flash-high).
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-repro-10286-"));
|
||||
process.env.DATA_DIR = TEST_DATA_DIR;
|
||||
process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "test-repro-10286-secret";
|
||||
|
||||
const caps = await import("../../src/lib/modelCapabilities.ts");
|
||||
const core = await import("../../src/lib/db/core.ts");
|
||||
const rulesDb = await import("../../src/lib/db/reasoningRoutingRules.ts");
|
||||
const policy = await import("../../src/lib/reasoningRouting/policy.ts");
|
||||
|
||||
async function resetStorage() {
|
||||
core.resetDbInstance();
|
||||
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
||||
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
|
||||
rulesDb.invalidateReasoningRoutingRuleCache();
|
||||
}
|
||||
|
||||
function ruleInput(patch: Record<string, unknown> = {}) {
|
||||
return {
|
||||
name: "Enable thinking on gemini-3.5-flash",
|
||||
description: "",
|
||||
scope: "global",
|
||||
apiKeyId: null,
|
||||
comboId: null,
|
||||
connectionId: null,
|
||||
modelPattern: "gemini-3.5-flash",
|
||||
sourceEffort: "any",
|
||||
requestTags: [],
|
||||
tagMatchMode: "any",
|
||||
effortMode: "inherit",
|
||||
targetEffort: null,
|
||||
targetKind: "keep",
|
||||
targetModel: null,
|
||||
targetComboId: null,
|
||||
budgetAction: "preserve",
|
||||
budgetTokens: null,
|
||||
priority: 0,
|
||||
enabled: true,
|
||||
...patch,
|
||||
};
|
||||
}
|
||||
|
||||
test.beforeEach(resetStorage);
|
||||
test.after(async () => {
|
||||
await resetStorage();
|
||||
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test("gemini-3.5-flash (AI Studio provider) resolves as thinking-capable", () => {
|
||||
const resolved = caps.getResolvedModelCapabilities({
|
||||
provider: "gemini",
|
||||
model: "gemini-3.5-flash",
|
||||
});
|
||||
assert.equal(resolved.supportsThinking, true);
|
||||
});
|
||||
|
||||
test("reasoning_effort 'high' on gemini-3.5-flash is NOT rejected by routing policy", async () => {
|
||||
await rulesDb.createReasoningRoutingRule(ruleInput());
|
||||
const decision = await policy.resolveReasoningRoutingRule({
|
||||
sourceModel: "gemini/gemini-3.5-flash",
|
||||
sourceModelAliases: ["gemini-3.5-flash"],
|
||||
sourceEffort: "high",
|
||||
hasReasoningSignal: true,
|
||||
});
|
||||
assert.ok(decision, "a matching rule must produce a decision");
|
||||
assert.equal(decision.capability, "supported");
|
||||
});
|
||||
@@ -71,7 +71,7 @@ test("OpenAI -> Gemini request strips encrypted from Codex collaboration tool pa
|
||||
],
|
||||
};
|
||||
|
||||
const result = openaiToGeminiRequest("gemini-3.5-flash-low", body, false) as {
|
||||
const result = openaiToGeminiRequest("gemini-3.7-flash-low", body, false) as {
|
||||
tools?: Array<{ functionDeclarations?: Array<{ parameters: unknown }> }>;
|
||||
};
|
||||
|
||||
|
||||
@@ -14,6 +14,16 @@ const SAMPLE = {
|
||||
supportedGenerationMethods: ["generateContent", "countTokens", "batchGenerateContent"],
|
||||
thinking: true,
|
||||
},
|
||||
{
|
||||
name: "models/gemini-3.5-flash",
|
||||
displayName: "Gemini 3.5 Flash",
|
||||
supportedGenerationMethods: ["generateContent"],
|
||||
},
|
||||
{
|
||||
name: "models/gemini-3.5-flash-lite",
|
||||
displayName: "Gemini 3.5 Flash Lite",
|
||||
supportedGenerationMethods: ["generateContent"],
|
||||
},
|
||||
{
|
||||
name: "models/gemini-3-pro-image-preview",
|
||||
displayName: "Gemini 3 Pro Image Preview",
|
||||
@@ -48,6 +58,12 @@ test("parseGeminiModelsList strips the models/ prefix and maps display name", ()
|
||||
assert.deepEqual(flash!.supportedEndpoints, ["chat"]);
|
||||
});
|
||||
|
||||
test("parseGeminiModelsList excludes retired Gemini 3.5 Flash but keeps Flash Lite", () => {
|
||||
const ids = parseGeminiModelsList(SAMPLE).map((model) => model.id);
|
||||
assert.equal(ids.includes("gemini-3.5-flash"), false);
|
||||
assert.equal(ids.includes("gemini-3.5-flash-lite"), true);
|
||||
});
|
||||
|
||||
test("parseGeminiModelsList maps generateContent image models to the chat endpoint", () => {
|
||||
const models = parseGeminiModelsList(SAMPLE);
|
||||
const proImage = models.find((m) => m.id === "gemini-3-pro-image-preview");
|
||||
|
||||
@@ -56,7 +56,7 @@ test("OpenAI -> Gemini request strips strict from OpenAI-style function tool par
|
||||
],
|
||||
};
|
||||
|
||||
const result = openaiToGeminiRequest("gemini-3.5-flash-low", body, false) as {
|
||||
const result = openaiToGeminiRequest("gemini-3.7-flash-low", body, false) as {
|
||||
tools?: Array<{ functionDeclarations?: Array<{ parameters: unknown }> }>;
|
||||
};
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ test("buildUrl uses chat/completions endpoint for gemini models", () => {
|
||||
};
|
||||
// Gemini has no native shim on Copilot — it stays on /chat/completions.
|
||||
assert.strictEqual(
|
||||
executor.buildUrl("gemini-3.5-flash", true, 0, credentials),
|
||||
executor.buildUrl("gemini-3.7-flash", true, 0, credentials),
|
||||
"https://ghe.company.com/chat/completions"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -279,6 +279,7 @@ test("antigravity sync dynamically builds and saves mitmAlias mappings", async (
|
||||
mode: "sync",
|
||||
fetchedModels: [
|
||||
{ id: "gemini-3.5-flash", name: "Gemini 3.5 Flash" },
|
||||
{ id: "gemini-3.7-flash-high", name: "Gemini 3.7 Flash High" },
|
||||
{ id: "custom-antigravity-model", name: "Custom Antigravity Model" },
|
||||
],
|
||||
});
|
||||
@@ -289,8 +290,13 @@ test("antigravity sync dynamically builds and saves mitmAlias mappings", async (
|
||||
const mitmMappings = await modelsDb.getMitmAlias("antigravity");
|
||||
console.log("MITM MAPPINGS IN TEST:", mitmMappings);
|
||||
|
||||
// Should contain standard mapping
|
||||
assert.equal(mitmMappings["gemini-3.5-flash"], "antigravity/gemini-3.5-flash");
|
||||
// Retired models reported by upstream must not be imported or mapped.
|
||||
assert.equal(mitmMappings["gemini-3.5-flash"], undefined);
|
||||
assert.equal(
|
||||
models.some((model) => model.id === "gemini-3.5-flash"),
|
||||
false
|
||||
);
|
||||
assert.equal(mitmMappings["gemini-3.7-flash-high"], "antigravity/gemini-3.7-flash-high");
|
||||
assert.equal(mitmMappings["custom-antigravity-model"], "antigravity/custom-antigravity-model");
|
||||
|
||||
// Removed Antigravity 2.0 preview/agent aliases must not be reintroduced.
|
||||
|
||||
@@ -154,21 +154,14 @@ test("unknown models keep maxOutputTokens null instead of using a generic defaul
|
||||
);
|
||||
});
|
||||
|
||||
test("provider-neutral Gemini 3.5 tier IDs retain their non-thinking capabilities", () => {
|
||||
test("retired Gemini 3.5 Flash IDs have no provider-neutral model specs", () => {
|
||||
for (const modelId of [
|
||||
"gemini-3.5-flash",
|
||||
"gemini-3.5-flash-extra-low",
|
||||
"gemini-3.5-flash-low",
|
||||
"gemini-3-flash-agent",
|
||||
]) {
|
||||
const spec = MODEL_SPECS[modelId];
|
||||
assert.ok(spec, `missing exact MODEL_SPECS entry for ${modelId}`);
|
||||
const capabilities = modelCapabilities.getResolvedModelCapabilities(modelId);
|
||||
assert.equal(capabilities.contextWindow, 1048576, modelId);
|
||||
assert.equal(capabilities.maxOutputTokens, 65536, modelId);
|
||||
// These ids encode the upstream reasoning tier and do not accept a client-supplied effort.
|
||||
assert.equal(capabilities.supportsThinking, false, modelId);
|
||||
assert.equal(capabilities.supportsTools, true, modelId);
|
||||
assert.equal(capabilities.supportsVision, true, modelId);
|
||||
assert.equal(MODEL_SPECS[modelId], undefined, modelId);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -702,6 +702,7 @@ test("v1 models catalog exposes current Antigravity aliases without retired mode
|
||||
assert.equal(ids.has("antigravity/gemini-3.6-flash-high"), false);
|
||||
assert.equal(ids.has("antigravity/gemini-3.6-flash-medium"), false);
|
||||
assert.equal(ids.has("antigravity/gemini-3.6-flash-low"), false);
|
||||
assert.equal(ids.has("antigravity/gemini-3.5-flash"), false);
|
||||
assert.equal(ids.has("antigravity/gemini-3.5-flash-extra-low"), false);
|
||||
assert.equal(ids.has("antigravity/gemini-3.5-flash-low"), false);
|
||||
assert.equal(ids.has("antigravity/gemini-3-flash-agent"), false);
|
||||
|
||||
@@ -60,7 +60,7 @@ test("buildOmniRouteResponseMetaHeaders keeps ASCII model header values unchange
|
||||
});
|
||||
|
||||
test("buildOmniRouteResponseMetaHeaders percent-encodes non-ASCII model header values", () => {
|
||||
const model = "free-mix/[假流式]gemini-3.5-flash";
|
||||
const model = "free-mix/[假流式]gemini-3.7-flash";
|
||||
const headers = buildOmniRouteResponseMetaHeaders({
|
||||
provider: "openai",
|
||||
model,
|
||||
|
||||
@@ -288,7 +288,7 @@ test("hidden provider models are filtered from per-model quota rows", () => {
|
||||
});
|
||||
const hidden = providerLimitUtils.collectHiddenQuotaModelIds("antigravity", {
|
||||
models: [{ id: "antigravity/gpt-oss-120b-medium", isHidden: true }],
|
||||
modelCompatOverrides: [{ id: "gemini-3.5-flash", isHidden: true }],
|
||||
modelCompatOverrides: [{ id: "gemini-3.7-flash", isHidden: true }],
|
||||
});
|
||||
const visible = providerLimitUtils.filterHiddenModelQuotas("antigravity", quotas, hidden);
|
||||
|
||||
|
||||
@@ -394,7 +394,7 @@ test("parseSSEToGeminiResponse extracts tool calls from textual format", () => {
|
||||
})}`,
|
||||
].join("\n");
|
||||
|
||||
const parsed = parseSSEToGeminiResponse(rawSSE, "gemini-3.5-flash-low");
|
||||
const parsed = parseSSEToGeminiResponse(rawSSE, "gemini-3.7-flash-low");
|
||||
|
||||
assert.ok(parsed);
|
||||
assert.equal(parsed.choices[0].finish_reason, "tool_calls");
|
||||
|
||||
@@ -232,14 +232,14 @@ test("createSSEStream passthrough converts textual tool-call content into struct
|
||||
id: "chatcmpl_textual_tool",
|
||||
object: "chat.completion.chunk",
|
||||
created: 1,
|
||||
model: "antigravity/gemini-3.5-flash-low",
|
||||
model: "antigravity/gemini-3.7-flash-low",
|
||||
choices: [{ index: 0, delta: { role: "assistant", content: toolText } }],
|
||||
})}\n\n`,
|
||||
`data: ${JSON.stringify({
|
||||
id: "chatcmpl_textual_tool",
|
||||
object: "chat.completion.chunk",
|
||||
created: 1,
|
||||
model: "antigravity/gemini-3.5-flash-low",
|
||||
model: "antigravity/gemini-3.7-flash-low",
|
||||
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
|
||||
})}\n\n`,
|
||||
],
|
||||
@@ -247,7 +247,7 @@ test("createSSEStream passthrough converts textual tool-call content into struct
|
||||
mode: "passthrough",
|
||||
sourceFormat: FORMATS.OPENAI,
|
||||
provider: "antigravity",
|
||||
model: "antigravity/gemini-3.5-flash-low",
|
||||
model: "antigravity/gemini-3.7-flash-low",
|
||||
body: {
|
||||
messages: [{ role: "user", content: "inspect db" }],
|
||||
},
|
||||
@@ -284,21 +284,21 @@ test("createSSEStream passthrough converts split textual tool-call content at co
|
||||
id: "chatcmpl_split_textual_tool",
|
||||
object: "chat.completion.chunk",
|
||||
created: 1,
|
||||
model: "antigravity/gemini-3.5-flash-low",
|
||||
model: "antigravity/gemini-3.7-flash-low",
|
||||
choices: [{ index: 0, delta: { role: "assistant", content: chunks[0] } }],
|
||||
})}\n\n`,
|
||||
`data: ${JSON.stringify({
|
||||
id: "chatcmpl_split_textual_tool",
|
||||
object: "chat.completion.chunk",
|
||||
created: 1,
|
||||
model: "antigravity/gemini-3.5-flash-low",
|
||||
model: "antigravity/gemini-3.7-flash-low",
|
||||
choices: [{ index: 0, delta: { content: chunks[1] } }],
|
||||
})}\n\n`,
|
||||
`data: ${JSON.stringify({
|
||||
id: "chatcmpl_split_textual_tool",
|
||||
object: "chat.completion.chunk",
|
||||
created: 1,
|
||||
model: "antigravity/gemini-3.5-flash-low",
|
||||
model: "antigravity/gemini-3.7-flash-low",
|
||||
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
|
||||
})}\n\n`,
|
||||
],
|
||||
@@ -306,7 +306,7 @@ test("createSSEStream passthrough converts split textual tool-call content at co
|
||||
mode: "passthrough",
|
||||
sourceFormat: FORMATS.OPENAI,
|
||||
provider: "antigravity",
|
||||
model: "antigravity/gemini-3.5-flash-low",
|
||||
model: "antigravity/gemini-3.7-flash-low",
|
||||
body: { messages: [{ role: "user", content: "inspect db" }] },
|
||||
onComplete(payload) {
|
||||
onCompletePayload = payload;
|
||||
@@ -340,28 +340,28 @@ test("createSSEStream passthrough handles textual tool-call content split inside
|
||||
id: "chatcmpl_split_prefix_textual_tool",
|
||||
object: "chat.completion.chunk",
|
||||
created: 1,
|
||||
model: "antigravity/gemini-3.5-flash-low",
|
||||
model: "antigravity/gemini-3.7-flash-low",
|
||||
choices: [{ index: 0, delta: { role: "assistant", content: chunks[0] } }],
|
||||
})}\n\n`,
|
||||
`data: ${JSON.stringify({
|
||||
id: "chatcmpl_split_prefix_textual_tool",
|
||||
object: "chat.completion.chunk",
|
||||
created: 1,
|
||||
model: "antigravity/gemini-3.5-flash-low",
|
||||
model: "antigravity/gemini-3.7-flash-low",
|
||||
choices: [{ index: 0, delta: { content: chunks[1] } }],
|
||||
})}\n\n`,
|
||||
`data: ${JSON.stringify({
|
||||
id: "chatcmpl_split_prefix_textual_tool",
|
||||
object: "chat.completion.chunk",
|
||||
created: 1,
|
||||
model: "antigravity/gemini-3.5-flash-low",
|
||||
model: "antigravity/gemini-3.7-flash-low",
|
||||
choices: [{ index: 0, delta: { content: chunks[2] } }],
|
||||
})}\n\n`,
|
||||
`data: ${JSON.stringify({
|
||||
id: "chatcmpl_split_prefix_textual_tool",
|
||||
object: "chat.completion.chunk",
|
||||
created: 1,
|
||||
model: "antigravity/gemini-3.5-flash-low",
|
||||
model: "antigravity/gemini-3.7-flash-low",
|
||||
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
|
||||
})}\n\n`,
|
||||
],
|
||||
@@ -369,7 +369,7 @@ test("createSSEStream passthrough handles textual tool-call content split inside
|
||||
mode: "passthrough",
|
||||
sourceFormat: FORMATS.OPENAI,
|
||||
provider: "antigravity",
|
||||
model: "antigravity/gemini-3.5-flash-low",
|
||||
model: "antigravity/gemini-3.7-flash-low",
|
||||
body: { messages: [{ role: "user", content: "inspect db" }] },
|
||||
onComplete(payload) {
|
||||
onCompletePayload = payload;
|
||||
@@ -515,14 +515,14 @@ Arguments: {"path":"/opt/OmniRoute/src","target":"files"}`;
|
||||
id: "chatcmpl_unknown_textual_tool",
|
||||
object: "chat.completion.chunk",
|
||||
created: 1,
|
||||
model: "antigravity/gemini-3.5-flash-low",
|
||||
model: "antigravity/gemini-3.7-flash-low",
|
||||
choices: [{ index: 0, delta: { role: "assistant", content: toolText } }],
|
||||
})}\n\n`,
|
||||
`data: ${JSON.stringify({
|
||||
id: "chatcmpl_unknown_textual_tool",
|
||||
object: "chat.completion.chunk",
|
||||
created: 1,
|
||||
model: "antigravity/gemini-3.5-flash-low",
|
||||
model: "antigravity/gemini-3.7-flash-low",
|
||||
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
|
||||
})}\n\n`,
|
||||
],
|
||||
@@ -530,7 +530,7 @@ Arguments: {"path":"/opt/OmniRoute/src","target":"files"}`;
|
||||
mode: "passthrough",
|
||||
sourceFormat: FORMATS.OPENAI,
|
||||
provider: "antigravity",
|
||||
model: "antigravity/gemini-3.5-flash-low",
|
||||
model: "antigravity/gemini-3.7-flash-low",
|
||||
body: {
|
||||
messages: [{ role: "user", content: "inspect files" }],
|
||||
tools: [
|
||||
@@ -561,14 +561,14 @@ test("createSSEStream passthrough suppresses malformed textual tool-call content
|
||||
id: "chatcmpl_malformed_textual_tool",
|
||||
object: "chat.completion.chunk",
|
||||
created: 1,
|
||||
model: "antigravity/gemini-3.5-flash-low",
|
||||
model: "antigravity/gemini-3.7-flash-low",
|
||||
choices: [{ index: 0, delta: { role: "assistant", content: malformedToolText } }],
|
||||
})}\n\n`,
|
||||
`data: ${JSON.stringify({
|
||||
id: "chatcmpl_malformed_textual_tool",
|
||||
object: "chat.completion.chunk",
|
||||
created: 1,
|
||||
model: "antigravity/gemini-3.5-flash-low",
|
||||
model: "antigravity/gemini-3.7-flash-low",
|
||||
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
|
||||
})}\n\n`,
|
||||
],
|
||||
@@ -576,7 +576,7 @@ test("createSSEStream passthrough suppresses malformed textual tool-call content
|
||||
mode: "passthrough",
|
||||
sourceFormat: FORMATS.OPENAI,
|
||||
provider: "antigravity",
|
||||
model: "antigravity/gemini-3.5-flash-low",
|
||||
model: "antigravity/gemini-3.7-flash-low",
|
||||
body: { messages: [{ role: "user", content: "inspect db" }] },
|
||||
onComplete(payload) {
|
||||
onCompletePayload = payload;
|
||||
@@ -617,7 +617,7 @@ test("createSSEStream suppresses malformed compact textual tool-call content", a
|
||||
targetFormat: FORMATS.ANTIGRAVITY,
|
||||
sourceFormat: FORMATS.OPENAI,
|
||||
provider: "antigravity",
|
||||
model: "antigravity/gemini-3.5-flash-low",
|
||||
model: "antigravity/gemini-3.7-flash-low",
|
||||
body: { messages: [{ role: "user", content: "inspect files" }] },
|
||||
onComplete(payload) {
|
||||
onCompletePayload = payload;
|
||||
@@ -1024,7 +1024,7 @@ Arguments: {"command":"systemctl status omniroute"}`;
|
||||
response: {
|
||||
id: "resp_textual_tool",
|
||||
object: "response",
|
||||
model: "antigravity/gemini-3.5-flash-low",
|
||||
model: "antigravity/gemini-3.7-flash-low",
|
||||
status: "completed",
|
||||
output: [],
|
||||
usage: { input_tokens: 10, output_tokens: 4, total_tokens: 14 },
|
||||
@@ -1038,7 +1038,7 @@ Arguments: {"command":"systemctl status omniroute"}`;
|
||||
sourceFormat: FORMATS.OPENAI_RESPONSES,
|
||||
clientResponseFormat: FORMATS.OPENAI_RESPONSES,
|
||||
provider: "antigravity",
|
||||
model: "antigravity/gemini-3.5-flash-low",
|
||||
model: "antigravity/gemini-3.7-flash-low",
|
||||
body: {
|
||||
input: "check service",
|
||||
tools: [{ type: "function", name: "terminal", parameters: { type: "object" } }],
|
||||
|
||||
@@ -30,6 +30,7 @@ test("T28: antigravity static catalog exposes only callable Gemini tier IDs", ()
|
||||
assert.ok(!staticIds.includes("gemini-3.6-flash-high"));
|
||||
assert.ok(!staticIds.includes("gemini-3.6-flash-medium"));
|
||||
assert.ok(!staticIds.includes("gemini-3.6-flash-low"));
|
||||
assert.ok(!staticIds.includes("gemini-3.5-flash"));
|
||||
assert.ok(!staticIds.includes("gemini-3.5-flash-extra-low"));
|
||||
assert.ok(!staticIds.includes("gemini-3.5-flash-low"));
|
||||
assert.ok(!staticIds.includes("gemini-3-flash-agent"));
|
||||
|
||||
@@ -607,7 +607,7 @@ test("OpenAI -> Antigravity wraps Gemini requests in a Cloud Code envelope", ()
|
||||
|
||||
test("OpenAI -> Antigravity Gemini omits signature-less historical tool calls and keeps response context", () => {
|
||||
const result = openaiToAntigravityRequest(
|
||||
"gemini-3.5-flash-low",
|
||||
"gemini-3.7-flash-low",
|
||||
{
|
||||
messages: [
|
||||
{ role: "user", content: "Update todo" },
|
||||
@@ -686,7 +686,7 @@ test("OpenAI -> Antigravity Gemini omits signature-less historical tool calls an
|
||||
|
||||
test("OpenAI -> Antigravity preserves multiple signature-less historical tool responses as context", () => {
|
||||
const result = openaiToAntigravityRequest(
|
||||
"gemini-3.5-flash-low",
|
||||
"gemini-3.7-flash-low",
|
||||
{
|
||||
messages: [
|
||||
{ role: "user", content: "Inspect OmniRoute config" },
|
||||
@@ -747,7 +747,7 @@ test("OpenAI -> Antigravity preserves signed Gemini tool calls in native form",
|
||||
storeGeminiThoughtSignature(buildGeminiThoughtSignatureKey(ns, toolId), "SIG_AG_SIGNED_XYZ");
|
||||
|
||||
const result = openaiToAntigravityRequest(
|
||||
"gemini-3.5-flash-low",
|
||||
"gemini-3.7-flash-low",
|
||||
{
|
||||
messages: [
|
||||
{ role: "user", content: "Read status" },
|
||||
@@ -787,7 +787,7 @@ test("OpenAI -> Antigravity preserves signed Gemini tool calls in native form",
|
||||
|
||||
test("OpenAI -> Antigravity escapes signature-less tool response context content", () => {
|
||||
const result = openaiToAntigravityRequest(
|
||||
"gemini-3.5-flash-low",
|
||||
"gemini-3.7-flash-low",
|
||||
{
|
||||
messages: [
|
||||
{ role: "user", content: "Inspect previous output" },
|
||||
|
||||
@@ -346,7 +346,7 @@ test("Gemini stream: converts textual Tool call block to structured tool_calls",
|
||||
const result = geminiToOpenAIResponse(
|
||||
{
|
||||
responseId: "resp-textual-tool",
|
||||
modelVersion: "gemini-3.5-flash-low",
|
||||
modelVersion: "gemini-3.7-flash-low",
|
||||
candidates: [
|
||||
{
|
||||
content: {
|
||||
@@ -386,7 +386,7 @@ test("Gemini stream: routes textual reasoning tags to reasoning_content before t
|
||||
const result = geminiToOpenAIResponse(
|
||||
{
|
||||
responseId: "resp-textual-thought-tool",
|
||||
modelVersion: "gemini-3.5-flash-high",
|
||||
modelVersion: "gemini-3.7-flash-high",
|
||||
candidates: [
|
||||
{
|
||||
content: {
|
||||
@@ -431,7 +431,7 @@ test("Gemini stream: keeps textual reasoning hidden across split chunks", () =>
|
||||
const first = geminiToOpenAIResponse(
|
||||
{
|
||||
responseId: "resp-split-thought",
|
||||
modelVersion: "gemini-3.5-flash-high",
|
||||
modelVersion: "gemini-3.7-flash-high",
|
||||
candidates: [{ content: { parts: [{ text: "§54§ <tho" }] } }],
|
||||
},
|
||||
state
|
||||
@@ -444,7 +444,7 @@ test("Gemini stream: keeps textual reasoning hidden across split chunks", () =>
|
||||
const second = geminiToOpenAIResponse(
|
||||
{
|
||||
responseId: "resp-split-thought",
|
||||
modelVersion: "gemini-3.5-flash-high",
|
||||
modelVersion: "gemini-3.7-flash-high",
|
||||
candidates: [{ content: { parts: [{ text: "ught\nNeed to inspect" }] } }],
|
||||
},
|
||||
state
|
||||
@@ -459,7 +459,7 @@ test("Gemini stream: keeps textual reasoning hidden across split chunks", () =>
|
||||
const third = geminiToOpenAIResponse(
|
||||
{
|
||||
responseId: "resp-split-thought",
|
||||
modelVersion: "gemini-3.5-flash-high",
|
||||
modelVersion: "gemini-3.7-flash-high",
|
||||
candidates: [{ content: { parts: [{ text: " more</tho" }] } }],
|
||||
},
|
||||
state
|
||||
@@ -472,7 +472,7 @@ test("Gemini stream: keeps textual reasoning hidden across split chunks", () =>
|
||||
const fourth = geminiToOpenAIResponse(
|
||||
{
|
||||
responseId: "resp-split-thought",
|
||||
modelVersion: "gemini-3.5-flash-high",
|
||||
modelVersion: "gemini-3.7-flash-high",
|
||||
candidates: [{ content: { parts: [{ text: "ught>Visible answer" }] } }],
|
||||
},
|
||||
state
|
||||
@@ -498,7 +498,7 @@ test("Gemini stream: converts prefixed textual Tool call block with zero-width c
|
||||
const result = geminiToOpenAIResponse(
|
||||
{
|
||||
responseId: "resp-textual-tool-prefixed",
|
||||
modelVersion: "gemini-3.5-flash-low",
|
||||
modelVersion: "gemini-3.7-flash-low",
|
||||
candidates: [
|
||||
{
|
||||
content: {
|
||||
@@ -612,7 +612,7 @@ test("Gemini stream: unwraps native functionCall args when emitted as JSON strin
|
||||
const result = geminiToOpenAIResponse(
|
||||
{
|
||||
responseId: "resp-native-tool-json-string",
|
||||
modelVersion: "gemini-3.5-flash-low",
|
||||
modelVersion: "gemini-3.7-flash-low",
|
||||
candidates: [
|
||||
{
|
||||
content: {
|
||||
@@ -648,7 +648,7 @@ test("Gemini stream: converts JSON-string encoded textual Tool call arguments",
|
||||
const result = geminiToOpenAIResponse(
|
||||
{
|
||||
responseId: "resp-textual-tool-json-string",
|
||||
modelVersion: "gemini-3.5-flash-low",
|
||||
modelVersion: "gemini-3.7-flash-low",
|
||||
candidates: [
|
||||
{
|
||||
content: {
|
||||
@@ -685,7 +685,7 @@ test("Gemini stream: suppresses malformed textual Tool call marker", () => {
|
||||
const result = geminiToOpenAIResponse(
|
||||
{
|
||||
responseId: "resp-textual-tool-malformed",
|
||||
modelVersion: "gemini-3.5-flash-low",
|
||||
modelVersion: "gemini-3.7-flash-low",
|
||||
candidates: [
|
||||
{
|
||||
content: {
|
||||
@@ -717,7 +717,7 @@ test("Gemini stream: handles textual Tool call block split across chunks", () =>
|
||||
const state = createStreamingState();
|
||||
const chunk1 = {
|
||||
responseId: "resp-split",
|
||||
modelVersion: "gemini-3.5-flash-low",
|
||||
modelVersion: "gemini-3.7-flash-low",
|
||||
candidates: [
|
||||
{
|
||||
content: {
|
||||
@@ -732,7 +732,7 @@ test("Gemini stream: handles textual Tool call block split across chunks", () =>
|
||||
};
|
||||
const chunk2 = {
|
||||
responseId: "resp-split",
|
||||
modelVersion: "gemini-3.5-flash-low",
|
||||
modelVersion: "gemini-3.7-flash-low",
|
||||
candidates: [
|
||||
{
|
||||
content: {
|
||||
@@ -768,7 +768,7 @@ test("Gemini stream: does not swallow false positive textual tool call in backti
|
||||
const state = createStreamingState();
|
||||
const chunk1 = {
|
||||
responseId: "resp-false-positive",
|
||||
modelVersion: "gemini-3.5-flash-low",
|
||||
modelVersion: "gemini-3.7-flash-low",
|
||||
candidates: [
|
||||
{
|
||||
content: {
|
||||
@@ -783,7 +783,7 @@ test("Gemini stream: does not swallow false positive textual tool call in backti
|
||||
};
|
||||
const chunk2 = {
|
||||
responseId: "resp-false-positive",
|
||||
modelVersion: "gemini-3.5-flash-low",
|
||||
modelVersion: "gemini-3.7-flash-low",
|
||||
candidates: [
|
||||
{
|
||||
content: {
|
||||
@@ -817,7 +817,7 @@ test("Gemini stream: does not swallow terminated trailing false positive textual
|
||||
const state = createStreamingState();
|
||||
const chunk1 = {
|
||||
responseId: "resp-false-positive-terminated",
|
||||
modelVersion: "gemini-3.5-flash-low",
|
||||
modelVersion: "gemini-3.7-flash-low",
|
||||
candidates: [
|
||||
{
|
||||
content: {
|
||||
@@ -842,7 +842,7 @@ test("Gemini stream: flushes left part before textual tool call candidate and fl
|
||||
const state = createStreamingState() as any;
|
||||
const chunk1 = {
|
||||
responseId: "resp-test-flush-left",
|
||||
modelVersion: "gemini-3.5-flash-low",
|
||||
modelVersion: "gemini-3.7-flash-low",
|
||||
candidates: [
|
||||
{
|
||||
content: {
|
||||
@@ -884,7 +884,7 @@ test("Gemini stream: splits mid-stream partial candidate but preserves tool call
|
||||
const state = createStreamingState() as any;
|
||||
const chunk1 = {
|
||||
responseId: "resp-test-split-candidate",
|
||||
modelVersion: "gemini-3.5-flash-low",
|
||||
modelVersion: "gemini-3.7-flash-low",
|
||||
candidates: [
|
||||
{
|
||||
content: {
|
||||
@@ -932,7 +932,7 @@ test("Gemini stream: index mismatch regression test with zero-width characters i
|
||||
const result = geminiToOpenAIResponse(
|
||||
{
|
||||
responseId: "resp-textual-tool-index-mismatch",
|
||||
modelVersion: "gemini-3.5-flash-low",
|
||||
modelVersion: "gemini-3.7-flash-low",
|
||||
candidates: [
|
||||
{
|
||||
content: {
|
||||
@@ -964,7 +964,7 @@ test("Gemini stream: partial tool call with (empty) prefix check at chunk end do
|
||||
const state = createStreamingState();
|
||||
const chunk1 = {
|
||||
responseId: "resp-empty-leak",
|
||||
modelVersion: "gemini-3.5-flash-low",
|
||||
modelVersion: "gemini-3.7-flash-low",
|
||||
candidates: [
|
||||
{
|
||||
content: {
|
||||
@@ -1011,7 +1011,7 @@ test("Gemini stream: parses textual tool call that starts in a subsequent chunk
|
||||
const state = createStreamingState() as any;
|
||||
const chunk1 = {
|
||||
responseId: "resp-test-after-prose",
|
||||
modelVersion: "gemini-3.5-flash-low",
|
||||
modelVersion: "gemini-3.7-flash-low",
|
||||
candidates: [
|
||||
{
|
||||
content: {
|
||||
@@ -1060,7 +1060,7 @@ test("Gemini stream: checks lastParen before lastBracket when identifying partia
|
||||
// Имитируем чанк, который кончается на частичный "(empty)[Tool call:" маркер, например "(em"
|
||||
const chunk1 = {
|
||||
responseId: "resp-test-empty-partial",
|
||||
modelVersion: "gemini-3.5-flash-low",
|
||||
modelVersion: "gemini-3.7-flash-low",
|
||||
candidates: [
|
||||
{
|
||||
content: {
|
||||
@@ -1187,7 +1187,7 @@ test("Gemini stream: partial textual tool call survives a reasoning-only chunk",
|
||||
geminiToOpenAIResponse(
|
||||
{
|
||||
responseId: "resp-interleave",
|
||||
modelVersion: "gemini-3.5-flash-low",
|
||||
modelVersion: "gemini-3.7-flash-low",
|
||||
candidates: [
|
||||
{ content: { parts: [{ text: '[Tool call: terminal]\nArguments: {"command":"ls' }] } },
|
||||
],
|
||||
@@ -1200,7 +1200,7 @@ test("Gemini stream: partial textual tool call survives a reasoning-only chunk",
|
||||
geminiToOpenAIResponse(
|
||||
{
|
||||
responseId: "resp-interleave",
|
||||
modelVersion: "gemini-3.5-flash-low",
|
||||
modelVersion: "gemini-3.7-flash-low",
|
||||
candidates: [{ content: { parts: [{ text: "<thinking>pondering</thinking>" }] } }],
|
||||
},
|
||||
state
|
||||
@@ -1221,7 +1221,7 @@ test("Gemini stream: partial textual tool call survives a reasoning-only chunk",
|
||||
geminiToOpenAIResponse(
|
||||
{
|
||||
responseId: "resp-interleave",
|
||||
modelVersion: "gemini-3.5-flash-low",
|
||||
modelVersion: "gemini-3.7-flash-low",
|
||||
candidates: [{ content: { parts: [{ text: '"}' }] }, finishReason: "STOP" }],
|
||||
},
|
||||
state
|
||||
|
||||
Reference in New Issue
Block a user