Repository navigation
Description
antigravity-direct: a specific-function tool_choice is flattened to functionCallingConfig: ANY — the function the client required is never pinned, and any declared function may be called instead
Repo: developing-today/code · Main: df9381ff · File: bin/antigravity-direct.mjs:355-357 (landed today in b0058ec7)
Summary
OpenAI's Chat Completions API lets a caller force one specific function: tool_choice: { "type": "function", "function": { "name": "get_weather" } }. buildV1Request translates tool_choice to the Cloud Code / Gemini functionCallingConfig like this:
if (body.tool_choice) {
const mode = body.tool_choice === 'none' ? 'NONE' : body.tool_choice === 'auto' ? 'AUTO' : 'ANY';
request.toolConfig = { functionCallingConfig: { mode } };
}Every non-none/auto value — including the specific-function object — becomes bare ANY, and the allowedFunctionNames field that functionCallingConfig provides for exactly this case is never set. ANY means "must call some declared function", not "must call this function": a client that required get_weather can be answered with a call to any other declared function (in the probe: send_email).
The string forms are fine ("none" → NONE, "auto" → AUTO, "required" → ANY is the correct semantic). Only the object form loses information.
Evidence (verified this run, real module)
Laptop probe (~/git/fleet/probe-2026-10-30-laneA-antigrav-body/; bin/antigravity-direct.mjs md5 de11cfa24df8200d66fd788422667ef8 identical on sandbox main df9381ff and the laptop; stub secret-tool + stub upstream recording every payload — no real keyring read, no real Google call). Sent:
{
"model": "gemini-3.8-flash",
"messages": [{ "role": "user", "content": "weather?" }],
"tools": [
{ "type": "function", "function": { "name": "get_weather", "parameters": { "type": "object", "properties": {} } } },
{ "type": "function", "function": { "name": "send_email", "parameters": { "type": "object", "properties": {} } } }
],
"tool_choice": { "type": "function", "function": { "name": "get_weather" } }
}The recorded upstream streamGenerateContent payload carried both declarations but:
"toolConfig": { "functionCallingConfig": { "mode": "ANY" } }— no allowedFunctionNames, nothing naming get_weather. The response to the client was a normal HTTP 200, so the downgrade is silent.
Why it matters
Callers use a specific-function tool_choice as a control-flow guarantee (structured extraction, a router that must invoke exactly one named tool, a safety gate that must not fire side-effecting tools). Through this door the guarantee silently degrades to "some function": the model may invoke a different — possibly side-effecting — declared function, and the client, which never opted into that, will execute it under the assumption its choice was honoured. The fleet's own pattern already preserves the choice end-to-end: bin/muse-proxy.mjs:1161-1164 maps the object form through ({type:'function', name}), pinned by bin/muse-proxy.test.mjs:598,648.
Acceptance criteria
- A specific-function
tool_choice({type:"function", function:{name:"X"}}) produces an upstreamfunctionCallingConfigthat restricts calling toX(e.g.mode: "ANY"withallowedFunctionNames: ["X"]— triage pins the exact upstream shape), whereXis one of the declared functions. - String forms are unchanged:
"none"→NONE,"auto"→AUTO,"required"→ANY(no allowed list). - A
tool_choicenaming a function that was not declared does not silently widen to unrestrictedANY— it is rejected with a 400 or handled by an explicit, documented fallback (triage pins which). - Regression tests in a new
bin/antigravity-direct.test.mjs(stub-upstream payload recording, in the style ofbin/muse-proxy.test.mjs:598) cover the specific-function case and the three string forms; requests withouttool_choicesend notoolConfig, as today.
Dedup notes: searched open + closed issues this run for tool_choice / functionCallingConfig / allowedFunctionNames against antigravity — no hit. Distinct from #2821 (same file, request-body shape guard; this is a translation-fidelity bug on a well-formed object body).
Activity
dezren39 commentedon Oct 30, 2026
🤖 Agent note · filed
10 remaining items
dezren39 commentedon Nov 2, 2026
agent-note:v1 — picked up — Lane D
dezren39 commentedon Nov 2, 2026
agent-note:v1 — PR opened, parked on CI — Lane D
Merge pull request #4233 from developing-today/queue/5427-2822-tool-c…
dezren39 commentedon Nov 2, 2026
agent-note:v1 — done — Lane D