Skip to content

feat: add Google Antigravity provider and harden tool-call compatibility#343

Open
mrbeandev wants to merge 10 commits intosipeed:mainfrom
mrbeandev:feat/antigravity-provider
Open

feat: add Google Antigravity provider and harden tool-call compatibility#343
mrbeandev wants to merge 10 commits intosipeed:mainfrom
mrbeandev:feat/antigravity-provider

Conversation

@mrbeandev
Copy link

Summary

  • add full Google Antigravity (Cloud Code Assist) provider support with OAuth login flow, model handling, docs, and CLI wiring
  • fix multiple Antigravity tool-call compatibility issues discovered during real Telegram/Coolify testing (missing function names, invalid tool-call history ordering, thought signature preservation)
  • include deployment and runtime improvements already on this branch (Coolify support, Telegram command improvements, health endpoints, provider reliability/test updates)

Validation

  • go test ./pkg/providers ./pkg/agent ./pkg/tools
  • go build -o build/picoclaw ./cmd/picoclaw
  • manual verification: picoclaw agent with Antigravity + tool calls (web_fetch flow)

Security Check

  • scanned this branch for obvious secrets before opening the PR (access_token, refresh_token, ya29.*, private keys) and found no committed credential artifacts.

Copy link
Contributor

@lukemilby lukemilby left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Theres a lot of feature added here. I would create a PR for each addition, telegram, coolify, antigravity provider.

please address comments and do not use AI to respond.

regexp.MustCompile(`\bdd\s+if=`),
regexp.MustCompile(`>\s*/dev/sd[a-z]\b`), // Block writes to disk devices (but allow /dev/null)
regexp.MustCompile(`\b(shutdown|reboot|poweroff)\b`),
regexp.MustCompile(`:\(\)\s*\{.*\};\s*:`),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this removed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accidental removal — it got lost during a rebase when I added the background execution parameter. The fork bomb pattern needs to stay. Will restore it in the next push.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please just provide a link to Antigravity Auth doc vs having AI rewrite them.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point. Will replace the doc with a link to the official Cloud Code Assist setup guide: https://cloud.google.com/code/docs/vscode/get-started and keep just the picoclaw-specific config bits (the OAuth scopes and CLI flags).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

picoclaw already has docker files please remove

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. The Coolify-specific docker files were for my own deployment and should not be in upstream. Will remove docker-compose-coolify.yml, Dockerfile.coolify, and entrypoint-coolify.sh from this PR. If the Coolify setup is useful to anyone, I can document it separately in a wiki or discussion post.

}, nil
}

func normalizeProviderToolCall(tc providers.ToolCall) providers.ToolCall {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we normalizing the tool call? This should be done when calling the tool. Is your an LLM please do not answer this question its for the PR creator

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Antigravity endpoint (cloudcode-pa.googleapis.com) wraps the Gemini response in an extra envelope. When I parse that envelope, the function name sometimes lands only in ToolCall.Name and not in ToolCall.Function.Name, or vice versa. The normalize function copies the name and args into both locations so the downstream tool dispatch in toolloop.go always finds them.

You are right that this should live closer to where the tool is actually called rather than being a generic post-processing step. I will move this logic into the Antigravity provider's response parser where the envelope is unwrapped, so it does not touch the common path.

Arguments string `json:"arguments"`
Name string `json:"name"`
Arguments string `json:"arguments"`
ThoughtSignature string `json:"thought_signature"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where at is thought signature in a function tool call? Please provide link to confirm Thought Signature needs to be in the Function struct.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gemini 3 (and 2.5 with thinking enabled) returns a thought_signature alongside each functionCall part. If you strip it out when rebuilding session history, the next API call fails with a 400: "Function call is missing a thought_signature."

This is documented at:

For the OpenAI-compatible endpoint (which picoclaw uses), the signature appears in extra_content.google.thought_signature on the tool call object. For the native Gemini endpoint, it is thoughtSignature on the Part alongside functionCall.

Since picoclaw uses the OpenAI-compatible format, the thought_signature field on the Function struct is where I store it after parsing so it can be re-serialized on the next turn. Without it, Gemini 3 Flash and Pro reject the request. This fixes #79 and #161.

}
}

func authModelsCmd() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generic Name. This looks to only handle Antigravity auth only. Move auth to provider.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right — authModelsCmd is misleading since it only handles the Antigravity OAuth flow. I will rename it to something specific like antigravityAuthCmd and move the auth logic into the Antigravity provider package so it follows the same pattern as the other providers. The CLI entry point will just call the provider method.

Comment on lines -164 to -171
if tc.Type == "function" && tc.Function != nil {
name = tc.Function.Name
if tc.Function.Arguments != "" {
if err := json.Unmarshal([]byte(tc.Function.Arguments), &arguments); err != nil {
arguments["raw"] = tc.Function.Arguments
}
}
} else if tc.Function != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we not need to handle nested function object anymore?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we still need it. In the new code, the nested function object is still handled — the if tc.Function != nil check at line 181 covers both the type == "function" case and the legacy case without a type field. The difference is I collapsed the two branches (which had identical logic) into one since both did the same thing: extract Name and Arguments from tc.Function.

The old code:

if tc.Type == "function" && tc.Function != nil {
    // extract name + args
} else if tc.Function != nil {
    // exact same extraction
}

The new code:

if tc.Function != nil {
    // extract name + args (covers both cases)
}

The only actual change is adding ThoughtSignature extraction and populating both ToolCall.Function and ToolCall.Name/ToolCall.Arguments for consistent access downstream. The nested function struct is still used.

@mrbeandev
Copy link
Author

Thanks for the thorough review Luke. You're right — this PR accumulated too many concerns. I'll split it into separate PRs:

  1. Coolify deployment (docker-compose, Dockerfile, entrypoint)
  2. Telegram command improvements (slash commands, model switching)
  3. Antigravity provider (oauth, provider, docs)
  4. Gemini thought_signature fix (this one addresses Gemini 3 Flash Preview: Function call fails due to missing thought_signature #79 and Incompatibility with Gemini 3 Pro/Flash due to Mandatory Thought Signatures #161 independently)

Addressing each inline comment below.

@mrbeandev mrbeandev force-pushed the feat/antigravity-provider branch from 69d8e3a to 84110aa Compare February 17, 2026 14:37
@mrbeandev
Copy link
Author

Force-pushed a clean branch. Rebased fresh from upstream/main and cherry-picked only the 10 Antigravity-related commits.

Removed (will be separate PRs if needed):

What remains (13 files, ~2400 lines):

  • Antigravity provider (pkg/providers/antigravity_provider.go + tests)
  • OAuth flow with headless support (pkg/auth/oauth.go)
  • CLI wiring (cmd/picoclaw/main.go)
  • Thought signature preservation for Gemini 3 (pkg/providers/http_provider.go, pkg/agent/loop.go)
  • Tool call normalization for Antigravity envelope (pkg/tools/toolloop.go)
  • Docs (docs/ANTIGRAVITY_AUTH.md, docs/ANTIGRAVITY_USAGE.md)

Build and tests pass: go build + go test ./pkg/providers ./pkg/agent ./pkg/tools

yinwm added a commit to yinwm/picoclaw that referenced this pull request Feb 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants