fix(models): synthesize antigravity Gemini 3.1 pro high/low models (#22899)

* Models: add antigravity Gemini 3.1 forward-compat

* models: propagate availability to Gemini 3.1 dot IDs

* test(models): format Gemini 3.1 forward-compat test

* test(models): type Gemini 3.1 forward-compat fixtures

* models: add changelog note for antigravity gemini 3.1 forward-compat

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
Phineas1500
2026-02-22 19:11:39 -05:00
committed by GitHub
parent 5c7c37a02a
commit 320b62265d
5 changed files with 236 additions and 2 deletions

View File

@@ -701,6 +701,7 @@ Docs: https://docs.openclaw.ai
- Agents/Context: apply configured model `contextWindow` overrides after provider discovery so `lookupContextTokens()` honors operator config values (including discovery-failure paths). (#17404) Thanks @michaelbship and @vignesh07.
- Agents/Context: derive `lookupContextTokens()` from auth-available model metadata and keep the smallest discovered context window for duplicate model ids, preventing cross-provider cache collisions from overestimating session context limits. (#17586) Thanks @githabideri and @vignesh07.
- Agents/OpenAI: force `store=true` for direct OpenAI Responses/Codex runs to preserve multi-turn server-side conversation state, while leaving proxy/non-OpenAI endpoints unchanged. (#16803) Thanks @mark9232 and @vignesh07.
- Models/Antigravity: synthesize Gemini 3.1 Pro high/low model IDs (dash and dot forms) from Gemini 3 templates so `models list` and `/model` remain usable while upstream catalogs catch up. (#22492) Thanks @Phineas1500.
- Memory/FTS: make `buildFtsQuery` Unicode-aware so non-ASCII queries (including CJK) produce keyword tokens instead of falling back to vector-only search. (#17672) Thanks @KinGP5471.
- Auto-reply/Compaction: resolve `memory/YYYY-MM-DD.md` placeholders with timezone-aware runtime dates and append a `Current time:` line to memory-flush turns, preventing wrong-year memory filenames without making the system prompt time-variant. (#17603, #17633) Thanks @nicholaspapadam-wq and @vignesh07.
- Auth/Cooldowns: auto-expire stale auth profile cooldowns when `cooldownUntil` or `disabledUntil` timestamps have passed, and reset `errorCount` so the next transient failure does not immediately escalate to a disproportionately long cooldown. Handles `cooldownUntil` and `disabledUntil` independently. (#3604) Thanks @nabbilkhan.

View File

@@ -0,0 +1,72 @@
import type { Api, Model } from "@mariozechner/pi-ai";
import { describe, expect, it } from "vitest";
import { resolveForwardCompatModel } from "./model-forward-compat.js";
import type { ModelRegistry } from "./pi-model-discovery.js";
function makeRegistry(): ModelRegistry {
const templates = new Map<string, Model<Api>>();
templates.set("google-antigravity/gemini-3-pro-high", {
id: "gemini-3-pro-high",
name: "Gemini 3 Pro High",
provider: "google-antigravity",
api: "google-antigravity",
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200000,
maxTokens: 64000,
reasoning: true,
} as Model<Api>);
templates.set("google-antigravity/gemini-3-pro-low", {
id: "gemini-3-pro-low",
name: "Gemini 3 Pro Low",
provider: "google-antigravity",
api: "google-antigravity",
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200000,
maxTokens: 64000,
reasoning: true,
} as Model<Api>);
const registry = {
find: (provider: string, modelId: string) => templates.get(`${provider}/${modelId}`) ?? null,
} as unknown as ModelRegistry;
return registry;
}
describe("resolveForwardCompatModel (google-antigravity Gemini 3.1)", () => {
it("resolves gemini-3-1-pro-high from gemini-3-pro-high template", () => {
const model = resolveForwardCompatModel(
"google-antigravity",
"gemini-3-1-pro-high",
makeRegistry(),
);
expect(model?.provider).toBe("google-antigravity");
expect(model?.id).toBe("gemini-3-1-pro-high");
});
it("resolves gemini-3-1-pro-low from gemini-3-pro-low template", () => {
const model = resolveForwardCompatModel(
"google-antigravity",
"gemini-3-1-pro-low",
makeRegistry(),
);
expect(model?.provider).toBe("google-antigravity");
expect(model?.id).toBe("gemini-3-1-pro-low");
});
it("supports dot-notation model ids", () => {
const high = resolveForwardCompatModel(
"google-antigravity",
"gemini-3.1-pro-high",
makeRegistry(),
);
const low = resolveForwardCompatModel(
"google-antigravity",
"gemini-3.1-pro-low",
makeRegistry(),
);
expect(high?.id).toBe("gemini-3.1-pro-high");
expect(low?.id).toBe("gemini-3.1-pro-low");
});
});

View File

@@ -26,6 +26,12 @@ const ANTIGRAVITY_OPUS_THINKING_TEMPLATE_MODEL_IDS = [
"claude-opus-4-5-thinking",
"claude-opus-4.5-thinking",
] as const;
const ANTIGRAVITY_GEMINI_31_PRO_HIGH_MODEL_ID = "gemini-3-1-pro-high";
const ANTIGRAVITY_GEMINI_31_PRO_DOT_HIGH_MODEL_ID = "gemini-3.1-pro-high";
const ANTIGRAVITY_GEMINI_31_PRO_LOW_MODEL_ID = "gemini-3-1-pro-low";
const ANTIGRAVITY_GEMINI_31_PRO_DOT_LOW_MODEL_ID = "gemini-3.1-pro-low";
const ANTIGRAVITY_GEMINI_31_PRO_HIGH_TEMPLATE_MODEL_IDS = ["gemini-3-pro-high"] as const;
const ANTIGRAVITY_GEMINI_31_PRO_LOW_TEMPLATE_MODEL_IDS = ["gemini-3-pro-low"] as const;
export const ANTIGRAVITY_OPUS_46_FORWARD_COMPAT_CANDIDATES = [
{
@@ -34,10 +40,25 @@ export const ANTIGRAVITY_OPUS_46_FORWARD_COMPAT_CANDIDATES = [
"google-antigravity/claude-opus-4-5-thinking",
"google-antigravity/claude-opus-4.5-thinking",
],
availabilityAliasIds: [] as const,
},
{
id: ANTIGRAVITY_OPUS_46_MODEL_ID,
templatePrefixes: ["google-antigravity/claude-opus-4-5", "google-antigravity/claude-opus-4.5"],
availabilityAliasIds: [] as const,
},
] as const;
export const ANTIGRAVITY_GEMINI_31_FORWARD_COMPAT_CANDIDATES = [
{
id: ANTIGRAVITY_GEMINI_31_PRO_HIGH_MODEL_ID,
templatePrefixes: ["google-antigravity/gemini-3-pro-high"],
availabilityAliasIds: [ANTIGRAVITY_GEMINI_31_PRO_DOT_HIGH_MODEL_ID],
},
{
id: ANTIGRAVITY_GEMINI_31_PRO_LOW_MODEL_ID,
templatePrefixes: ["google-antigravity/gemini-3-pro-low"],
availabilityAliasIds: [ANTIGRAVITY_GEMINI_31_PRO_DOT_LOW_MODEL_ID],
},
] as const;
@@ -278,6 +299,40 @@ function resolveAntigravityOpus46ForwardCompatModel(
});
}
function resolveAntigravityGemini31ForwardCompatModel(
provider: string,
modelId: string,
modelRegistry: ModelRegistry,
): Model<Api> | undefined {
const normalizedProvider = normalizeProviderId(provider);
if (normalizedProvider !== "google-antigravity") {
return undefined;
}
const trimmedModelId = modelId.trim();
const lower = trimmedModelId.toLowerCase();
const isGemini31High =
lower === ANTIGRAVITY_GEMINI_31_PRO_HIGH_MODEL_ID ||
lower === ANTIGRAVITY_GEMINI_31_PRO_DOT_HIGH_MODEL_ID;
const isGemini31Low =
lower === ANTIGRAVITY_GEMINI_31_PRO_LOW_MODEL_ID ||
lower === ANTIGRAVITY_GEMINI_31_PRO_DOT_LOW_MODEL_ID;
if (!isGemini31High && !isGemini31Low) {
return undefined;
}
const templateIds = isGemini31High
? [...ANTIGRAVITY_GEMINI_31_PRO_HIGH_TEMPLATE_MODEL_IDS]
: [...ANTIGRAVITY_GEMINI_31_PRO_LOW_TEMPLATE_MODEL_IDS];
return cloneFirstTemplateModel({
normalizedProvider,
trimmedModelId,
templateIds,
modelRegistry,
});
}
export function resolveForwardCompatModel(
provider: string,
modelId: string,
@@ -288,6 +343,7 @@ export function resolveForwardCompatModel(
resolveAnthropicOpus46ForwardCompatModel(provider, modelId, modelRegistry) ??
resolveAnthropicSonnet46ForwardCompatModel(provider, modelId, modelRegistry) ??
resolveZaiGlm5ForwardCompatModel(provider, modelId, modelRegistry) ??
resolveAntigravityOpus46ForwardCompatModel(provider, modelId, modelRegistry)
resolveAntigravityOpus46ForwardCompatModel(provider, modelId, modelRegistry) ??
resolveAntigravityGemini31ForwardCompatModel(provider, modelId, modelRegistry)
);
}

View File

@@ -389,6 +389,99 @@ describe("models list/status", () => {
},
);
it.each([
{
name: "high",
configuredModelId: "gemini-3-1-pro-high",
templateId: "gemini-3-pro-high",
templateName: "Gemini 3 Pro High",
expectedKey: "google-antigravity/gemini-3-1-pro-high",
},
{
name: "low",
configuredModelId: "gemini-3-1-pro-low",
templateId: "gemini-3-pro-low",
templateName: "Gemini 3 Pro Low",
expectedKey: "google-antigravity/gemini-3-1-pro-low",
},
] as const)(
"models list resolves antigravity gemini 3.1 $name from gemini 3 template",
async ({ configuredModelId, templateId, templateName, expectedKey }) => {
const payload = await runGoogleAntigravityListCase({
configuredModelId,
templateId,
templateName,
});
expectAntigravityModel(payload, {
key: expectedKey,
available: false,
includesTags: true,
});
},
);
it.each([
{
name: "high",
configuredModelId: "gemini-3-1-pro-high",
templateId: "gemini-3-pro-high",
templateName: "Gemini 3 Pro High",
expectedKey: "google-antigravity/gemini-3-1-pro-high",
},
{
name: "low",
configuredModelId: "gemini-3-1-pro-low",
templateId: "gemini-3-pro-low",
templateName: "Gemini 3 Pro Low",
expectedKey: "google-antigravity/gemini-3-1-pro-low",
},
] as const)(
"models list marks synthesized antigravity gemini 3.1 $name as available when template is available",
async ({ configuredModelId, templateId, templateName, expectedKey }) => {
const payload = await runGoogleAntigravityListCase({
configuredModelId,
templateId,
templateName,
available: true,
});
expectAntigravityModel(payload, {
key: expectedKey,
available: true,
});
},
);
it.each([
{
name: "high",
configuredModelId: "gemini-3.1-pro-high",
templateId: "gemini-3-pro-high",
templateName: "Gemini 3 Pro High",
expectedKey: "google-antigravity/gemini-3.1-pro-high",
},
{
name: "low",
configuredModelId: "gemini-3.1-pro-low",
templateId: "gemini-3-pro-low",
templateName: "Gemini 3 Pro Low",
expectedKey: "google-antigravity/gemini-3.1-pro-low",
},
] as const)(
"models list marks dot-notation antigravity gemini 3.1 $name as available when template is available",
async ({ configuredModelId, templateId, templateName, expectedKey }) => {
const payload = await runGoogleAntigravityListCase({
configuredModelId,
templateId,
templateName,
available: true,
});
expectAntigravityModel(payload, {
key: expectedKey,
available: true,
});
},
);
it("models list prefers registry availability over provider auth heuristics", async () => {
const payload = await runGoogleAntigravityListCase({
configuredModelId: "claude-opus-4-6-thinking",

View File

@@ -8,6 +8,7 @@ import {
resolveEnvApiKey,
} from "../../agents/model-auth.js";
import {
ANTIGRAVITY_GEMINI_31_FORWARD_COMPAT_CANDIDATES,
ANTIGRAVITY_OPUS_46_FORWARD_COMPAT_CANDIDATES,
resolveForwardCompatModel,
} from "../../agents/model-forward-compat.js";
@@ -117,6 +118,9 @@ export async function loadModelRegistry(cfg: OpenClawConfig) {
for (const synthesized of synthesizedForwardCompat) {
if (hasAvailableTemplate(availableKeys, synthesized.templatePrefixes)) {
availableKeys.add(synthesized.key);
for (const aliasKey of synthesized.availabilityAliasKeys) {
availableKeys.add(aliasKey);
}
}
}
} catch (err) {
@@ -137,6 +141,7 @@ export async function loadModelRegistry(cfg: OpenClawConfig) {
type SynthesizedForwardCompat = {
key: string;
templatePrefixes: readonly string[];
availabilityAliasKeys: readonly string[];
};
function appendAntigravityForwardCompatModels(
@@ -145,8 +150,12 @@ function appendAntigravityForwardCompatModels(
): { models: Model<Api>[]; synthesizedForwardCompat: SynthesizedForwardCompat[] } {
const nextModels = [...models];
const synthesizedForwardCompat: SynthesizedForwardCompat[] = [];
const candidates = [
...ANTIGRAVITY_OPUS_46_FORWARD_COMPAT_CANDIDATES,
...ANTIGRAVITY_GEMINI_31_FORWARD_COMPAT_CANDIDATES,
];
for (const candidate of ANTIGRAVITY_OPUS_46_FORWARD_COMPAT_CANDIDATES) {
for (const candidate of candidates) {
const key = modelKey("google-antigravity", candidate.id);
const hasForwardCompat = nextModels.some((model) => modelKey(model.provider, model.id) === key);
if (hasForwardCompat) {
@@ -162,6 +171,9 @@ function appendAntigravityForwardCompatModels(
synthesizedForwardCompat.push({
key,
templatePrefixes: candidate.templatePrefixes,
availabilityAliasKeys: candidate.availabilityAliasIds.map((id) =>
modelKey("google-antigravity", id),
),
});
}