@cavi-ai/antigravity 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/doctor-contract-api.js +13 -0
- package/openclaw.plugin.json +72 -0
- package/package.json +73 -0
- package/setup-api.js +10 -0
- package/src/cli-backend.js +71 -0
- package/src/index.js +19 -0
- package/src/models.js +105 -0
- package/src/provider.js +215 -0
- package/src/register.js +24 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
- Add provider-owned guided setup that validates the external `agy` session
|
|
6
|
+
without storing an OpenClaw credential.
|
|
7
|
+
- Persist the provider connection on reconnect: guided and interactive reconnect
|
|
8
|
+
preserve existing provider options and return a non-secret `configPatch`
|
|
9
|
+
recording the Antigravity endpoint and model catalog.
|
|
10
|
+
- Refresh the static catalog for the models reported by `agy` 1.1.25.
|
|
11
|
+
- Accept model listings written to stdout or stderr and separated by tabs or
|
|
12
|
+
spaces.
|
|
13
|
+
- Give Antigravity provider requests focused guidance for using the local
|
|
14
|
+
`openclaw` CLI and MCP servers managed by `mcporter`, using a stable hook id.
|
|
15
|
+
|
|
16
|
+
## 0.1.0
|
|
17
|
+
|
|
18
|
+
- First public release under `@cavi-ai/antigravity` (the unscoped npm name
|
|
19
|
+
`openclaw-antigravity` is a different package).
|
|
20
|
+
- OpenClaw provider/cli-backend ownership metadata, package-root setup entry,
|
|
21
|
+
doctor contract, and CLI-owned auth recovery hints.
|
|
22
|
+
- Models via `antigravity-cli/<model>` using Google's Antigravity CLI (`agy`).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sasan Sotoodehfar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# @cavi-ai/antigravity
|
|
2
|
+
|
|
3
|
+
Use Google's Antigravity CLI (`agy`) as an [OpenClaw](https://github.com/openclaw/openclaw) model provider.
|
|
4
|
+
|
|
5
|
+
The plugin uses the Antigravity login already available to `agy`. It does not ask for or store an Antigravity API key.
|
|
6
|
+
|
|
7
|
+
**Documentation:** [Online docs](https://cavi-ai.xyz/docs/antigravity) ·
|
|
8
|
+
[Overview](docs/antigravity/source/pages/introduction/overview.md) ·
|
|
9
|
+
[Quickstart](docs/antigravity/source/pages/introduction/quickstart.md) ·
|
|
10
|
+
[Configuration](docs/antigravity/source/pages/guides/configuration.md) ·
|
|
11
|
+
[Contributing](CONTRIBUTING.md)
|
|
12
|
+
|
|
13
|
+
The pages under `docs/antigravity/source/` are the release source. A published
|
|
14
|
+
GitHub Release builds them into an immutable versioned artifact that cavi-home
|
|
15
|
+
ingests and serves at [cavi-ai.xyz/docs/antigravity](https://cavi-ai.xyz/docs/antigravity).
|
|
16
|
+
|
|
17
|
+
## Requirements
|
|
18
|
+
|
|
19
|
+
- OpenClaw 2026.7 or newer
|
|
20
|
+
- Node.js 20 or newer
|
|
21
|
+
- Antigravity CLI installed and signed in
|
|
22
|
+
|
|
23
|
+
Confirm that Antigravity is ready:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
agy models
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
openclaw plugins install @cavi-ai/antigravity
|
|
33
|
+
openclaw config set plugins.entries.antigravity.enabled true
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Restart the OpenClaw Gateway after installation.
|
|
37
|
+
|
|
38
|
+
## Connect
|
|
39
|
+
|
|
40
|
+
In OpenClaw, select **Antigravity CLI** and choose **Reconnect**. The plugin validates the existing `agy` session and makes its models available without creating a separate credential.
|
|
41
|
+
|
|
42
|
+
## Use
|
|
43
|
+
|
|
44
|
+
Models use the `antigravity-cli/<model>` format:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
openclaw agent --model antigravity-cli/gemini-3.1-pro-high -m "hello"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
If your OpenClaw configuration uses `agents.defaults.modelPolicy.allow`, add the models you want to use or allow `antigravity-cli/*`.
|
|
51
|
+
|
|
52
|
+
### Models
|
|
53
|
+
|
|
54
|
+
Run `agy models` for the live list. The plugin includes this catalog from `agy` 1.1.25:
|
|
55
|
+
|
|
56
|
+
| Model | Alias |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| `gemini-3.8-flash-high` | |
|
|
59
|
+
| `gemini-3.8-flash-medium` | |
|
|
60
|
+
| `gemini-3.8-flash-low` | |
|
|
61
|
+
| `gemini-3.7-flash-high` | |
|
|
62
|
+
| `gemini-3.7-flash-medium` | |
|
|
63
|
+
| `gemini-3.7-flash-low` | |
|
|
64
|
+
| `gemini-3.6-flash-high` | |
|
|
65
|
+
| `gemini-3.6-flash-medium` | `flash` |
|
|
66
|
+
| `gemini-3.6-flash-low` | `flash-lite` |
|
|
67
|
+
| `gemini-3.1-pro-high` | `pro` (default) |
|
|
68
|
+
| `gemini-3.1-pro-low` | |
|
|
69
|
+
| `claude-sonnet-4-6` | `sonnet` |
|
|
70
|
+
| `claude-opus-4-6-thinking` | `opus` |
|
|
71
|
+
| `gpt-oss-120b-medium` | `gpt-oss` |
|
|
72
|
+
|
|
73
|
+
New model IDs that are not yet in the catalog are passed through to `agy`.
|
|
74
|
+
|
|
75
|
+
## OpenClaw and MCP commands
|
|
76
|
+
|
|
77
|
+
When a request runs through this provider, Antigravity is told to:
|
|
78
|
+
|
|
79
|
+
- use the installed `openclaw` CLI for OpenClaw operations;
|
|
80
|
+
- use `mcporter` for external MCP servers it manages; and
|
|
81
|
+
- check each CLI's help before assuming command syntax.
|
|
82
|
+
|
|
83
|
+
This guidance is limited to Antigravity requests and does not change either tool's global configuration.
|
|
84
|
+
|
|
85
|
+
## Configuration
|
|
86
|
+
|
|
87
|
+
All settings are optional and live under `plugins.entries.antigravity.config`.
|
|
88
|
+
|
|
89
|
+
| Key | Default | Purpose |
|
|
90
|
+
| --- | --- | --- |
|
|
91
|
+
| `command` | `agy` | Path to the `agy` executable. |
|
|
92
|
+
| `mode` | `accept-edits` | Value passed to `agy --mode`, or `none` to omit it. |
|
|
93
|
+
| `skipPermissions` | `false` | Pass `--dangerously-skip-permissions`. Enable only if you accept tool runs without permission prompts. |
|
|
94
|
+
|
|
95
|
+
Example:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
openclaw config set plugins.entries.antigravity.config.command /absolute/path/to/agy
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Troubleshooting
|
|
102
|
+
|
|
103
|
+
- Run `agy models` to confirm that the CLI is installed and signed in.
|
|
104
|
+
- If the provider is missing, confirm the plugin is enabled and restart the Gateway.
|
|
105
|
+
- If OpenClaw rejects a model before it runs, check `agents.defaults.modelPolicy.allow`.
|
|
106
|
+
|
|
107
|
+
## Limitations
|
|
108
|
+
|
|
109
|
+
- Responses are returned after `agy` finishes; token streaming is not available.
|
|
110
|
+
- Inline image input is not supported by `agy --print`.
|
|
111
|
+
- Per-token cost is reported as zero because Antigravity is subscription-backed.
|
|
112
|
+
- Context-window values are conservative because the CLI does not publish per-model limits.
|
|
113
|
+
|
|
114
|
+
## Development
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npm test # plugin behaviour
|
|
118
|
+
npm run docs:test # documentation build/verify/release tooling
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Releasing docs is automated: publish a GitHub Release `vX.Y.Z` (matching
|
|
122
|
+
`package.json`) and the `Publish release documentation` workflow builds the
|
|
123
|
+
versioned artifact, attaches it to the release, and dispatches cavi-home to
|
|
124
|
+
ingest it. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
MIT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Doctor contract auto-loaded from the plugin package root (or dist/).
|
|
2
|
+
// See OpenClaw doctor-contract-registry load paths.
|
|
3
|
+
|
|
4
|
+
export const sessionRouteStateOwners = [
|
|
5
|
+
{
|
|
6
|
+
id: "antigravity",
|
|
7
|
+
label: "Antigravity CLI",
|
|
8
|
+
providerIds: ["antigravity-cli"],
|
|
9
|
+
runtimeIds: ["antigravity-cli"],
|
|
10
|
+
cliSessionKeys: ["antigravity-cli", "antigravity", "agy"],
|
|
11
|
+
authProfilePrefixes: ["antigravity-cli:", "antigravity:", "agy:"],
|
|
12
|
+
},
|
|
13
|
+
];
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "antigravity",
|
|
3
|
+
"name": "Antigravity CLI",
|
|
4
|
+
"version": "0.2.0",
|
|
5
|
+
"description": "Runs Google's Antigravity CLI (agy) as a subscription-backed model provider.",
|
|
6
|
+
"enabledByDefault": false,
|
|
7
|
+
"activation": {
|
|
8
|
+
"onStartup": true,
|
|
9
|
+
"onProviders": ["antigravity-cli"]
|
|
10
|
+
},
|
|
11
|
+
"providers": ["antigravity-cli"],
|
|
12
|
+
"cliBackends": ["antigravity-cli"],
|
|
13
|
+
"autoEnableWhenConfiguredProviders": ["antigravity-cli"],
|
|
14
|
+
"syntheticAuthRefs": ["antigravity-cli"],
|
|
15
|
+
"setup": {
|
|
16
|
+
"providers": [
|
|
17
|
+
{
|
|
18
|
+
"id": "antigravity-cli"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"cliBackends": ["antigravity-cli"]
|
|
22
|
+
},
|
|
23
|
+
"providerAuthChoices": [
|
|
24
|
+
{
|
|
25
|
+
"provider": "antigravity-cli",
|
|
26
|
+
"method": "cli",
|
|
27
|
+
"choiceId": "antigravity-cli",
|
|
28
|
+
"appGuidedDiscovery": true,
|
|
29
|
+
"appGuidedActionLabel": "Reconnect",
|
|
30
|
+
"choiceLabel": "Antigravity CLI",
|
|
31
|
+
"choiceHint": "Use an existing Antigravity CLI (agy) login on this host. Run `agy models` to confirm.",
|
|
32
|
+
"groupId": "antigravity",
|
|
33
|
+
"groupLabel": "Antigravity",
|
|
34
|
+
"groupHint": "Subscription-backed models via agy",
|
|
35
|
+
"onboardingFeatured": true
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"uiHints": {
|
|
39
|
+
"command": {
|
|
40
|
+
"label": "agy binary",
|
|
41
|
+
"help": "Path to the agy executable. Prefer an absolute path after install; default `agy` resolves via PATH.",
|
|
42
|
+
"placeholder": "agy"
|
|
43
|
+
},
|
|
44
|
+
"mode": {
|
|
45
|
+
"label": "agy mode",
|
|
46
|
+
"help": "Value for `agy --mode` on print-mode runs, or `none` to omit the flag."
|
|
47
|
+
},
|
|
48
|
+
"skipPermissions": {
|
|
49
|
+
"label": "Skip permissions",
|
|
50
|
+
"help": "Passes --dangerously-skip-permissions to agy. Off by default. Only enable if you accept tool runs without agy permission prompts."
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"configSchema": {
|
|
54
|
+
"type": "object",
|
|
55
|
+
"additionalProperties": false,
|
|
56
|
+
"properties": {
|
|
57
|
+
"command": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"description": "Path to the agy binary. Defaults to `agy` on PATH. Prefer an absolute path; do not pass shell metacharacters."
|
|
60
|
+
},
|
|
61
|
+
"mode": {
|
|
62
|
+
"type": "string",
|
|
63
|
+
"description": "agy --mode value for print-mode runs, or \"none\" to omit the flag.",
|
|
64
|
+
"enum": ["accept-edits", "plan", "none"]
|
|
65
|
+
},
|
|
66
|
+
"skipPermissions": {
|
|
67
|
+
"type": "boolean",
|
|
68
|
+
"description": "Pass --dangerously-skip-permissions. Off by default."
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cavi-ai/antigravity",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Runs Google's Antigravity CLI (agy) as a subscription-backed OpenClaw model provider.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"openclaw",
|
|
7
|
+
"openclaw-plugin",
|
|
8
|
+
"antigravity",
|
|
9
|
+
"agy",
|
|
10
|
+
"gemini",
|
|
11
|
+
"llm"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/cavi-ai/openclaw-antigravity.git"
|
|
17
|
+
},
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/cavi-ai/openclaw-antigravity/issues"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/cavi-ai/openclaw-antigravity#readme",
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"type": "module",
|
|
26
|
+
"main": "./src/index.js",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": "./src/index.js"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"src",
|
|
32
|
+
"setup-api.js",
|
|
33
|
+
"doctor-contract-api.js",
|
|
34
|
+
"openclaw.plugin.json",
|
|
35
|
+
"README.md",
|
|
36
|
+
"CHANGELOG.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=20"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"openclaw": ">=2026.7.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependenciesMeta": {
|
|
46
|
+
"openclaw": {
|
|
47
|
+
"optional": true
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"openclaw": {
|
|
51
|
+
"extensions": ["./src/index.js"],
|
|
52
|
+
"runtimeExtensions": ["./src/index.js"],
|
|
53
|
+
"install": {
|
|
54
|
+
"npmSpec": "@cavi-ai/antigravity",
|
|
55
|
+
"minHostVersion": ">=2026.7.0"
|
|
56
|
+
},
|
|
57
|
+
"compat": {
|
|
58
|
+
"pluginApi": ">=2026.7",
|
|
59
|
+
"minGatewayVersion": "2026.7.0"
|
|
60
|
+
},
|
|
61
|
+
"build": {
|
|
62
|
+
"openclawVersion": "2026.7.0",
|
|
63
|
+
"pluginSdkVersion": "2026.7.0"
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"scripts": {
|
|
67
|
+
"test": "node --test test/*.test.mjs",
|
|
68
|
+
"docs:test": "node --test scripts/docs/*.test.mjs",
|
|
69
|
+
"docs:build": "node scripts/docs/build.mjs",
|
|
70
|
+
"docs:verify": "node scripts/docs/verify.mjs",
|
|
71
|
+
"docs:release": "node scripts/docs/release-artifact.mjs"
|
|
72
|
+
}
|
|
73
|
+
}
|
package/setup-api.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Lightweight setup entry. OpenClaw discovers package-root `setup-api.js`
|
|
2
|
+
// for descriptor-backed setup without loading the full runtime entry.
|
|
3
|
+
import { registerAntigravity } from "./src/register.js";
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
id: "antigravity",
|
|
7
|
+
name: "Antigravity CLI Setup",
|
|
8
|
+
description: "Setup hooks for the Antigravity CLI provider.",
|
|
9
|
+
register: registerAntigravity,
|
|
10
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// CLI backend that drives Google's Antigravity CLI (`agy`) in print mode.
|
|
2
|
+
//
|
|
3
|
+
// This is the `claude -p` pattern: OpenClaw owns the conversation, agy owns
|
|
4
|
+
// inference and auth. agy authenticates itself against the user's Antigravity
|
|
5
|
+
// subscription, so this backend carries no API key and no auth methods.
|
|
6
|
+
//
|
|
7
|
+
// `agy --print --output-format json` emits a single flat object:
|
|
8
|
+
// {"conversation_id":"…","status":"SUCCESS","response":"…","duration_seconds":…,
|
|
9
|
+
// "num_turns":1,"usage":{input_tokens,output_tokens,…}}
|
|
10
|
+
// Core's generic JSON reader already takes `response` as the assistant text and
|
|
11
|
+
// `sessionIdFields` for the resume handle, so no stream dialect is needed. agy's
|
|
12
|
+
// `--output-format stream-json` is its own dialect (event/step_update/result) and
|
|
13
|
+
// matches neither claude-stream-json nor gemini-stream-json; adopting it would
|
|
14
|
+
// require a core dialect, so print+json is used instead.
|
|
15
|
+
import { ANTIGRAVITY_MODEL_ALIASES } from "./models.js";
|
|
16
|
+
|
|
17
|
+
export const ANTIGRAVITY_BACKEND_ID = "antigravity-cli";
|
|
18
|
+
|
|
19
|
+
/** agy print mode cannot prompt a human, so tool calls need a non-review mode. */
|
|
20
|
+
export const DEFAULT_MODE = "accept-edits";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Builds the args every invocation shares.
|
|
24
|
+
*
|
|
25
|
+
* @param {{mode?: string, skipPermissions?: boolean}} [options]
|
|
26
|
+
*/
|
|
27
|
+
export function buildBaseArgs(options = {}) {
|
|
28
|
+
const args = ["--print", "{prompt}", "--output-format", "json"];
|
|
29
|
+
const mode = options.mode ?? DEFAULT_MODE;
|
|
30
|
+
if (mode !== "none") {
|
|
31
|
+
args.push("--mode", mode);
|
|
32
|
+
}
|
|
33
|
+
if (options.skipPermissions === true) {
|
|
34
|
+
args.push("--dangerously-skip-permissions");
|
|
35
|
+
}
|
|
36
|
+
return args;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {{command?: string, mode?: string, skipPermissions?: boolean}} [options]
|
|
41
|
+
* Plugin config from `plugins.entries.antigravity.config`.
|
|
42
|
+
*/
|
|
43
|
+
export function buildAntigravityCliBackend(options = {}) {
|
|
44
|
+
const base = buildBaseArgs(options);
|
|
45
|
+
return {
|
|
46
|
+
id: ANTIGRAVITY_BACKEND_ID,
|
|
47
|
+
// Standalone backend: agy's catalog (Gemini + Claude + GPT-OSS behind one
|
|
48
|
+
// subscription) belongs to no existing provider, so it owns direct
|
|
49
|
+
// `antigravity-cli/<model>` refs rather than aliasing a canonical provider.
|
|
50
|
+
modelProvider: ANTIGRAVITY_BACKEND_ID,
|
|
51
|
+
config: {
|
|
52
|
+
command: options.command?.trim() || "agy",
|
|
53
|
+
args: [...base],
|
|
54
|
+
// agy resumes by conversation id; there is no separate session concept.
|
|
55
|
+
resumeArgs: [...base, "--conversation", "{sessionId}"],
|
|
56
|
+
output: "json",
|
|
57
|
+
resumeOutput: "json",
|
|
58
|
+
input: "arg",
|
|
59
|
+
// agy takes the prompt as an argv value. Very long prompts blow the argv
|
|
60
|
+
// limit, so hand those to stdin instead.
|
|
61
|
+
maxPromptArgChars: 96_000,
|
|
62
|
+
modelArg: "--model",
|
|
63
|
+
modelAliases: { ...ANTIGRAVITY_MODEL_ALIASES },
|
|
64
|
+
sessionMode: "existing",
|
|
65
|
+
sessionIdFields: ["conversation_id"],
|
|
66
|
+
// One agy process at a time: concurrent print-mode runs against the same
|
|
67
|
+
// conversation interleave writes to its history.
|
|
68
|
+
serialize: true,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Antigravity CLI plugin — registers `agy` as an OpenClaw model provider backed
|
|
2
|
+
// by the Antigravity subscription, the slot gemini-cli used to fill.
|
|
3
|
+
import { registerAntigravity } from "./register.js";
|
|
4
|
+
|
|
5
|
+
export const PLUGIN_ID = "antigravity";
|
|
6
|
+
|
|
7
|
+
export const plugin = {
|
|
8
|
+
id: PLUGIN_ID,
|
|
9
|
+
name: "Antigravity CLI",
|
|
10
|
+
version: "0.2.0",
|
|
11
|
+
description: "Runs Google's Antigravity CLI (agy) as a subscription-backed model provider.",
|
|
12
|
+
register: registerAntigravity,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function register(api) {
|
|
16
|
+
plugin.register(api);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default plugin;
|
package/src/models.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Model catalog for Google's Antigravity CLI (`agy`).
|
|
2
|
+
//
|
|
3
|
+
// The list mirrors `agy models` as of agy 1.1.25. agy publishes no machine-readable
|
|
4
|
+
// catalog, so ids are refreshed by hand — run `agy models` after an `agy update`
|
|
5
|
+
// and reconcile. Unknown ids still work: they are passed through to `--model`
|
|
6
|
+
// verbatim, so a new agy model is usable before it is listed here.
|
|
7
|
+
|
|
8
|
+
/** agy's own model ids, from `agy models`. */
|
|
9
|
+
export const ANTIGRAVITY_MODEL_IDS = [
|
|
10
|
+
"gemini-3.8-flash-high",
|
|
11
|
+
"gemini-3.8-flash-medium",
|
|
12
|
+
"gemini-3.8-flash-low",
|
|
13
|
+
"gemini-3.7-flash-high",
|
|
14
|
+
"gemini-3.7-flash-medium",
|
|
15
|
+
"gemini-3.7-flash-low",
|
|
16
|
+
"gemini-3.6-flash-high",
|
|
17
|
+
"gemini-3.6-flash-medium",
|
|
18
|
+
"gemini-3.6-flash-low",
|
|
19
|
+
"gemini-3.1-pro-high",
|
|
20
|
+
"gemini-3.1-pro-low",
|
|
21
|
+
"claude-sonnet-4-6",
|
|
22
|
+
"claude-opus-4-6-thinking",
|
|
23
|
+
"gpt-oss-120b-medium",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export const ANTIGRAVITY_DEFAULT_MODEL = "gemini-3.1-pro-high";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Placeholder endpoint for the catalog row. `agy` owns the transport, so this is
|
|
30
|
+
* never dialed; it exists because `baseUrl` is a required provider field and a
|
|
31
|
+
* row without it breaks catalog assembly. Loopback keeps it classified as a
|
|
32
|
+
* local endpoint rather than an unresolvable public host.
|
|
33
|
+
*/
|
|
34
|
+
export const ANTIGRAVITY_BASE_URL = "http://127.0.0.1/antigravity-cli";
|
|
35
|
+
|
|
36
|
+
/** Adapter recorded on catalog rows. Unused at runtime for the same reason. */
|
|
37
|
+
export const ANTIGRAVITY_MODEL_API = "openai-completions";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Short names for the ids above. `agy` itself accepts only the full id, so these
|
|
41
|
+
* are expanded by the CLI backend before launch.
|
|
42
|
+
*/
|
|
43
|
+
export const ANTIGRAVITY_MODEL_ALIASES = {
|
|
44
|
+
pro: "gemini-3.1-pro-high",
|
|
45
|
+
flash: "gemini-3.6-flash-medium",
|
|
46
|
+
"flash-lite": "gemini-3.6-flash-low",
|
|
47
|
+
sonnet: "claude-sonnet-4-6",
|
|
48
|
+
opus: "claude-opus-4-6-thinking",
|
|
49
|
+
"gpt-oss": "gpt-oss-120b-medium",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Context windows are conservative floors, not published figures — Antigravity
|
|
53
|
+
// documents no per-model limits for the CLI. They exist so OpenClaw budgets and
|
|
54
|
+
// compacts sanely; raise them if agy is observed accepting more.
|
|
55
|
+
const CONTEXT_WINDOW_FLOOR = {
|
|
56
|
+
gemini: 1_000_000,
|
|
57
|
+
claude: 200_000,
|
|
58
|
+
other: 128_000,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function contextWindowFor(modelId) {
|
|
62
|
+
if (modelId.startsWith("gemini-")) {
|
|
63
|
+
return CONTEXT_WINDOW_FLOOR.gemini;
|
|
64
|
+
}
|
|
65
|
+
if (modelId.startsWith("claude-")) {
|
|
66
|
+
return CONTEXT_WINDOW_FLOOR.claude;
|
|
67
|
+
}
|
|
68
|
+
return CONTEXT_WINDOW_FLOOR.other;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Turns an agy model id into a human label, e.g. `gemini-3.1-pro-high` → `Gemini 3.1 Pro (high)`. */
|
|
72
|
+
export function labelForModelId(modelId) {
|
|
73
|
+
const match = /^(.*)-(high|medium|low|thinking)$/.exec(modelId);
|
|
74
|
+
const base = match ? match[1] : modelId;
|
|
75
|
+
const suffix = match ? match[2] : undefined;
|
|
76
|
+
const words = base
|
|
77
|
+
.split("-")
|
|
78
|
+
.map((part) => {
|
|
79
|
+
if (part === "gpt") return "GPT";
|
|
80
|
+
if (part === "oss") return "OSS";
|
|
81
|
+
if (/^\d/.test(part)) return part;
|
|
82
|
+
return part.charAt(0).toUpperCase() + part.slice(1);
|
|
83
|
+
})
|
|
84
|
+
.join(" ");
|
|
85
|
+
return suffix ? `${words} (${suffix})` : words;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Builds the catalog entries OpenClaw shows in model pickers. */
|
|
89
|
+
export function buildAntigravityModelCatalog() {
|
|
90
|
+
return ANTIGRAVITY_MODEL_IDS.map((id) => ({
|
|
91
|
+
id,
|
|
92
|
+
name: labelForModelId(id),
|
|
93
|
+
api: ANTIGRAVITY_MODEL_API,
|
|
94
|
+
// Thinking-tier ids and the pro tiers reason; the flash-low tiers do not
|
|
95
|
+
// advertise it. Treat everything except the explicit `-low` tiers as reasoning.
|
|
96
|
+
reasoning: !id.endsWith("-low"),
|
|
97
|
+
input: ["text"],
|
|
98
|
+
contextWindow: contextWindowFor(id),
|
|
99
|
+
maxTokens: 64_000,
|
|
100
|
+
// Antigravity is subscription-billed, not metered per token, so there is no
|
|
101
|
+
// per-token price to report. Zero here means "not separately billed", and
|
|
102
|
+
// keeps OpenClaw's cost accounting from inventing charges.
|
|
103
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
104
|
+
}));
|
|
105
|
+
}
|
package/src/provider.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// Provider registration for Antigravity (`agy`).
|
|
2
|
+
//
|
|
3
|
+
// agy owns the user's Antigravity OAuth session. The custom auth method below
|
|
4
|
+
// validates that CLI-owned session and records the provider's non-secret
|
|
5
|
+
// connection (models, endpoint) in config; OpenClaw stores no key for this provider.
|
|
6
|
+
import { execFile } from "node:child_process";
|
|
7
|
+
import { promisify } from "node:util";
|
|
8
|
+
import { ANTIGRAVITY_BACKEND_ID } from "./cli-backend.js";
|
|
9
|
+
import {
|
|
10
|
+
ANTIGRAVITY_BASE_URL,
|
|
11
|
+
ANTIGRAVITY_DEFAULT_MODEL,
|
|
12
|
+
ANTIGRAVITY_MODEL_ALIASES,
|
|
13
|
+
ANTIGRAVITY_MODEL_API,
|
|
14
|
+
buildAntigravityModelCatalog,
|
|
15
|
+
} from "./models.js";
|
|
16
|
+
|
|
17
|
+
export const ANTIGRAVITY_PROVIDER_ID = ANTIGRAVITY_BACKEND_ID;
|
|
18
|
+
const execFileAsync = promisify(execFile);
|
|
19
|
+
const COMMAND_TIMEOUT_MS = 15_000;
|
|
20
|
+
const COMMAND_MAX_BUFFER_BYTES = 1_048_576;
|
|
21
|
+
|
|
22
|
+
const MISSING_AUTH_MESSAGE =
|
|
23
|
+
"Antigravity CLI is not ready. Install Google's Antigravity CLI (`agy`), sign in with your Google subscription, then confirm with `agy models`. This provider stores no API key in OpenClaw.";
|
|
24
|
+
const RECONNECT_ERROR_MESSAGE =
|
|
25
|
+
"Antigravity CLI could not list models. Run `agy` in a terminal to sign in, then choose Reconnect again. OpenClaw stores no Antigravity credential.";
|
|
26
|
+
|
|
27
|
+
async function runCommand(command, args, context = {}) {
|
|
28
|
+
const result = await execFileAsync(command, args, {
|
|
29
|
+
env: context.env,
|
|
30
|
+
signal: context.signal,
|
|
31
|
+
timeout: COMMAND_TIMEOUT_MS,
|
|
32
|
+
maxBuffer: COMMAND_MAX_BUFFER_BYTES,
|
|
33
|
+
});
|
|
34
|
+
return [result.stdout, result.stderr].filter(Boolean).join("\n");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function throwIfAborted(signal) {
|
|
38
|
+
signal?.throwIfAborted?.();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isAbortError(error, signal) {
|
|
42
|
+
return signal?.aborted === true || error?.name === "AbortError" || error?.code === "ABORT_ERR";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Extracts model ids from the human-readable `agy models` table. */
|
|
46
|
+
export function parseAntigravityModelIds(output) {
|
|
47
|
+
const ids = [];
|
|
48
|
+
for (const line of String(output ?? "").split(/\r?\n/)) {
|
|
49
|
+
const match = /^\s*([a-z0-9][a-z0-9._-]*-[a-z0-9][a-z0-9._-]*)\s+\S.*$/u.exec(line);
|
|
50
|
+
const id = match?.[1];
|
|
51
|
+
if (!id || ids.includes(id)) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
ids.push(id);
|
|
55
|
+
}
|
|
56
|
+
return ids;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function chooseDetectedModel(modelIds) {
|
|
60
|
+
return modelIds.includes(ANTIGRAVITY_DEFAULT_MODEL)
|
|
61
|
+
? ANTIGRAVITY_DEFAULT_MODEL
|
|
62
|
+
: modelIds[0];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @param {{command?: string}} [options]
|
|
67
|
+
* @param {{runCommand?: typeof runCommand}} [dependencies]
|
|
68
|
+
*/
|
|
69
|
+
export function buildAntigravityProvider(options = {}, dependencies = {}) {
|
|
70
|
+
const command = options.command?.trim() || "agy";
|
|
71
|
+
const execute = dependencies.runCommand ?? runCommand;
|
|
72
|
+
|
|
73
|
+
const listModels = async (context) => {
|
|
74
|
+
throwIfAborted(context.signal);
|
|
75
|
+
const output = await execute(command, ["models"], context);
|
|
76
|
+
throwIfAborted(context.signal);
|
|
77
|
+
return parseAntigravityModelIds(output);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const detect = async (context) => {
|
|
81
|
+
try {
|
|
82
|
+
const modelId = chooseDetectedModel(await listModels(context));
|
|
83
|
+
return modelId
|
|
84
|
+
? {
|
|
85
|
+
modelRef: `${ANTIGRAVITY_PROVIDER_ID}/${modelId}`,
|
|
86
|
+
detail: `${modelId} via agy`,
|
|
87
|
+
}
|
|
88
|
+
: null;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (isAbortError(error, context.signal)) {
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// On a successful reconnect, hand OpenClaw a non-secret config patch so the
|
|
98
|
+
// provider's endpoint and model catalog persist. Shape matches the provider
|
|
99
|
+
// connection contract other CLI backends use (`configPatch.models.providers`).
|
|
100
|
+
const buildConnectionPatch = (config = {}) => {
|
|
101
|
+
const existing = config.models?.providers?.[ANTIGRAVITY_PROVIDER_ID] ?? {};
|
|
102
|
+
return {
|
|
103
|
+
models: {
|
|
104
|
+
mode: config.models?.mode ?? "merge",
|
|
105
|
+
providers: {
|
|
106
|
+
[ANTIGRAVITY_PROVIDER_ID]: {
|
|
107
|
+
...existing,
|
|
108
|
+
baseUrl: ANTIGRAVITY_BASE_URL,
|
|
109
|
+
api: ANTIGRAVITY_MODEL_API,
|
|
110
|
+
models:
|
|
111
|
+
Array.isArray(existing.models) && existing.models.length > 0
|
|
112
|
+
? existing.models
|
|
113
|
+
: buildAntigravityModelCatalog(),
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const validatedResult = (modelRef, config) => ({
|
|
121
|
+
profiles: [],
|
|
122
|
+
defaultModel: modelRef,
|
|
123
|
+
configPatch: buildConnectionPatch(config),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
id: ANTIGRAVITY_PROVIDER_ID,
|
|
128
|
+
label: "Antigravity CLI",
|
|
129
|
+
aliases: ["antigravity", "agy"],
|
|
130
|
+
envVars: [],
|
|
131
|
+
auth: [
|
|
132
|
+
{
|
|
133
|
+
id: "cli",
|
|
134
|
+
label: "Antigravity CLI",
|
|
135
|
+
hint: "Reconnect the CLI-owned Antigravity session and refresh available models",
|
|
136
|
+
kind: "custom",
|
|
137
|
+
appGuidedSetup: {
|
|
138
|
+
detectAvailability: async (context) => {
|
|
139
|
+
try {
|
|
140
|
+
throwIfAborted(context.signal);
|
|
141
|
+
await execute(command, ["--version"], context);
|
|
142
|
+
throwIfAborted(context.signal);
|
|
143
|
+
return true;
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (isAbortError(error, context.signal)) {
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
detect,
|
|
152
|
+
prepare: async (context) => {
|
|
153
|
+
const prefix = `${ANTIGRAVITY_PROVIDER_ID}/`;
|
|
154
|
+
if (!context.modelRef.startsWith(prefix)) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
const modelId = context.modelRef.slice(prefix.length);
|
|
158
|
+
try {
|
|
159
|
+
const available = await listModels(context);
|
|
160
|
+
return available.includes(modelId)
|
|
161
|
+
? validatedResult(context.modelRef, context.config)
|
|
162
|
+
: null;
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if (isAbortError(error, context.signal)) {
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
run: async (context) => {
|
|
172
|
+
const detected = await detect(context);
|
|
173
|
+
if (!detected) {
|
|
174
|
+
throw new Error(RECONNECT_ERROR_MESSAGE);
|
|
175
|
+
}
|
|
176
|
+
return validatedResult(detected.modelRef, context.config);
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
buildMissingAuthMessage: () => MISSING_AUTH_MESSAGE,
|
|
181
|
+
buildAuthDoctorHint: () => MISSING_AUTH_MESSAGE,
|
|
182
|
+
staticCatalog: {
|
|
183
|
+
order: "simple",
|
|
184
|
+
run: async () => ({
|
|
185
|
+
provider: {
|
|
186
|
+
// `agy` is the transport, so nothing here is ever dialed. Both fields
|
|
187
|
+
// are still required shape: a provider row without `baseUrl` fails
|
|
188
|
+
// catalog assembly, which takes down prepared-runtime publication for
|
|
189
|
+
// every agent, not just this provider.
|
|
190
|
+
baseUrl: ANTIGRAVITY_BASE_URL,
|
|
191
|
+
api: ANTIGRAVITY_MODEL_API,
|
|
192
|
+
defaultModel: ANTIGRAVITY_DEFAULT_MODEL,
|
|
193
|
+
models: buildAntigravityModelCatalog(),
|
|
194
|
+
},
|
|
195
|
+
}),
|
|
196
|
+
},
|
|
197
|
+
// agy accepts model ids this catalog has not caught up with. Rather than
|
|
198
|
+
// fail the run, pass an unknown id straight through to `--model`.
|
|
199
|
+
resolveDynamicModel: (ctx) => {
|
|
200
|
+
const modelId = typeof ctx?.modelId === "string" ? ctx.modelId.trim() : "";
|
|
201
|
+
if (!modelId) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
const resolved = ANTIGRAVITY_MODEL_ALIASES[modelId] ?? modelId;
|
|
205
|
+
return {
|
|
206
|
+
id: resolved,
|
|
207
|
+
provider: ANTIGRAVITY_PROVIDER_ID,
|
|
208
|
+
name: resolved,
|
|
209
|
+
api: ANTIGRAVITY_MODEL_API,
|
|
210
|
+
baseUrl: ANTIGRAVITY_BASE_URL,
|
|
211
|
+
input: ["text"],
|
|
212
|
+
};
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
package/src/register.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { buildAntigravityCliBackend } from "./cli-backend.js";
|
|
2
|
+
import { buildAntigravityProvider } from "./provider.js";
|
|
3
|
+
|
|
4
|
+
const ANTIGRAVITY_OPENCLAW_CONTEXT = [
|
|
5
|
+
"You are running through OpenClaw's Antigravity CLI model provider.",
|
|
6
|
+
"Use the installed `openclaw` CLI for OpenClaw operations and check its help before assuming command syntax.",
|
|
7
|
+
"Use `mcporter` for external MCP servers it manages and check its help before assuming command syntax.",
|
|
8
|
+
"Do not change global OpenClaw or mcporter configuration unless the user explicitly asks.",
|
|
9
|
+
].join(" ");
|
|
10
|
+
|
|
11
|
+
/** Shared by runtime entry and package-root setup-api. */
|
|
12
|
+
export function registerAntigravity(api) {
|
|
13
|
+
const config = api?.pluginConfig ?? {};
|
|
14
|
+
api.registerProvider(buildAntigravityProvider(config));
|
|
15
|
+
api.registerCliBackend(buildAntigravityCliBackend(config));
|
|
16
|
+
api.registerHook?.(
|
|
17
|
+
"before_prompt_build",
|
|
18
|
+
(_event, context) =>
|
|
19
|
+
context?.modelProviderId === "antigravity-cli"
|
|
20
|
+
? { prependContext: ANTIGRAVITY_OPENCLAW_CONTEXT }
|
|
21
|
+
: undefined,
|
|
22
|
+
{ registrationId: "antigravity-openclaw-cli-guidance" },
|
|
23
|
+
);
|
|
24
|
+
}
|