@tt-a1i/openpi 0.4.0 → 0.6.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/README.md +116 -46
- package/SETUP.md +29 -7
- package/THIRD_PARTY_NOTICES.md +16 -0
- package/assets/openpi-launch-card-v1.webp +0 -0
- package/bin/openpi.js +155 -0
- package/extensions/ai-providers/LICENSE.upstream +23 -0
- package/extensions/ai-providers/README.md +59 -0
- package/extensions/ai-providers/antigravity/credentials.ts +52 -0
- package/extensions/ai-providers/antigravity/discovery.ts +130 -0
- package/extensions/ai-providers/antigravity/google-conversion.ts +455 -0
- package/extensions/ai-providers/antigravity/models.ts +84 -0
- package/extensions/ai-providers/antigravity/oauth.ts +700 -0
- package/extensions/ai-providers/antigravity/provider.ts +1116 -0
- package/extensions/ai-providers/antigravity/routing.ts +340 -0
- package/extensions/ai-providers/antigravity/with-resolvers.d.ts +19 -0
- package/extensions/ai-providers/cursor/constants.ts +5 -0
- package/extensions/ai-providers/cursor/credentials.ts +14 -0
- package/extensions/ai-providers/cursor/discovery.ts +291 -0
- package/extensions/ai-providers/cursor/input-images.ts +106 -0
- package/extensions/ai-providers/cursor/models.ts +45 -0
- package/extensions/ai-providers/cursor/oauth.ts +263 -0
- package/extensions/ai-providers/cursor/proto.ts +1064 -0
- package/extensions/ai-providers/cursor/protobuf.ts +1171 -0
- package/extensions/ai-providers/cursor/provider.ts +1175 -0
- package/extensions/ai-providers/cursor/proxy.ts +213 -0
- package/extensions/ai-providers/cursor/with-resolvers.d.ts +12 -0
- package/extensions/ai-providers/index.ts +86 -0
- package/extensions/ai-providers/oauth-adapter.ts +81 -0
- package/extensions/ai-providers/usage.ts +10 -0
- package/extensions/background-terminals/index.ts +38 -3
- package/extensions/background-terminals/src/domain.ts +2 -0
- package/extensions/background-terminals/src/manager.ts +484 -106
- package/extensions/background-terminals/src/output.ts +33 -0
- package/extensions/background-terminals/src/prompt.ts +13 -5
- package/extensions/background-terminals/src/result-delivery.ts +47 -24
- package/extensions/clear-context/index.ts +83 -0
- package/extensions/context-pivot/index.ts +16 -6
- package/extensions/cron/index.ts +68 -27
- package/extensions/cron/schedule.ts +12 -2
- package/extensions/file-mutation-display/render.ts +17 -257
- package/extensions/file-search/src/binaries.ts +57 -41
- package/extensions/git-read/index.ts +1 -3
- package/extensions/model-info/cache-diagnostics.ts +220 -0
- package/extensions/model-info/index.ts +65 -33
- package/extensions/model-info/session-metrics.ts +96 -0
- package/extensions/plan-mode/bash-policy.ts +54 -9
- package/extensions/plan-mode/index.ts +82 -6
- package/extensions/post-edit/index.ts +16 -6
- package/extensions/sessions/git-stats.ts +258 -72
- package/extensions/sessions/index.ts +153 -86
- package/extensions/sessions/preview-cache.ts +104 -0
- package/extensions/sessions/preview-loader.ts +856 -0
- package/extensions/sessions/sessions.ts +43 -4
- package/extensions/setup/index.ts +138 -130
- package/extensions/shared/activity-status.ts +30 -0
- package/extensions/shared/agent-session-page.ts +319 -0
- package/extensions/shared/agent-tool-renderer.ts +218 -0
- package/extensions/shared/agent-transcript.ts +524 -0
- package/extensions/shared/capability-intent.ts +1 -1
- package/extensions/shared/child-session.ts +457 -21
- package/extensions/shared/completion-inbox.ts +193 -0
- package/extensions/shared/result-delivery.ts +34 -0
- package/extensions/shared/setup-config.ts +83 -34
- package/extensions/shared/setup-episode-state.ts +1 -1
- package/extensions/shared/structured-output.ts +154 -0
- package/extensions/shared/terminal-text.ts +110 -23
- package/extensions/shared/text-projection.ts +72 -15
- package/extensions/shared/tool-activity.ts +382 -0
- package/extensions/shared/tool-surface.ts +29 -2
- package/extensions/shared/transcript-viewport.ts +46 -0
- package/extensions/shared/web-observer-registry.ts +390 -0
- package/extensions/shared/worktree.ts +11 -0
- package/extensions/subagents/index.ts +313 -62
- package/extensions/subagents/navigation.ts +34 -5
- package/extensions/subagents/src/backend.ts +12 -1
- package/extensions/subagents/src/backends/pi.ts +450 -70
- package/extensions/subagents/src/domain.ts +21 -1
- package/extensions/subagents/src/manager.ts +39 -2
- package/extensions/subagents/src/prompt.ts +49 -7
- package/extensions/subagents/src/result-artifact.ts +36 -0
- package/extensions/subagents/src/result-delivery.ts +39 -14
- package/extensions/subagents/src/runtime.ts +15 -1
- package/extensions/subagents/src/ui/takeover.ts +73 -257
- package/extensions/subagents/src/ui/transcript.ts +38 -535
- package/extensions/subagents/src/ui/wait-result.ts +103 -15
- package/extensions/suggestions/src/ui.ts +10 -4
- package/extensions/tasks/index.ts +0 -3
- package/extensions/ui-customization/footer.ts +16 -45
- package/extensions/ui-customization/index.ts +0 -4
- package/extensions/user-input-fold/index.ts +42 -6
- package/extensions/web/index.ts +257 -0
- package/extensions/workflows/acceptance.ts +43 -19
- package/extensions/workflows/artifacts.ts +137 -47
- package/extensions/workflows/completion-projection.ts +459 -0
- package/extensions/workflows/coordinator.ts +8 -10
- package/extensions/workflows/dashboard.ts +175 -228
- package/extensions/workflows/handoff.ts +70 -16
- package/extensions/workflows/index.ts +501 -198
- package/extensions/workflows/journal.ts +148 -13
- package/extensions/workflows/model.ts +79 -5
- package/extensions/workflows/navigation.ts +32 -8
- package/extensions/workflows/progress-projection.ts +306 -0
- package/extensions/workflows/prompt.ts +70 -16
- package/extensions/workflows/replay-safety.ts +42 -21
- package/extensions/workflows/result-delivery.ts +214 -76
- package/extensions/workflows/retention.ts +599 -0
- package/extensions/workflows/runner.ts +389 -345
- package/extensions/workflows/sandbox-child.cjs +25 -3
- package/extensions/workflows/sandbox.ts +62 -8
- package/extensions/workflows/serialization.ts +325 -17
- package/extensions/workflows/tool-renderer.ts +22 -0
- package/extensions/workflows/transcript.ts +149 -0
- package/extensions/workspace-cleanup-guard/index.ts +54 -0
- package/extensions/workspace-cleanup-guard/workspace-provenance.ts +563 -0
- package/package.json +34 -14
- package/skills/subagents/REFERENCE.md +190 -0
- package/skills/subagents/SKILL.md +2 -1
- package/skills/workflows/REFERENCE.md +6 -4
- package/skills/workflows/SKILL.md +1 -1
- package/web/adapter/pi-adapter.ts +664 -0
- package/web/host/browser-launcher.ts +20 -0
- package/web/host/pi-coding-agent-entry.ts +162 -0
- package/web/host/static-assets.ts +4 -0
- package/web/host/terminal-status.ts +38 -0
- package/web/host/web-host.ts +1069 -0
- package/web/http-dispatcher.ts +125 -0
- package/web/protocol/types.ts +467 -0
- package/web/runtime/pi-runtime.ts +1206 -0
- package/web/runtime/types.ts +102 -0
- package/web/runtime/web-host-lease.ts +497 -0
- package/web/trace.ts +18 -0
- package/web/ui/app.js +1700 -0
- package/web/ui/index.html +142 -0
- package/web/ui/styles.css +680 -0
- package/web/vite.config.mjs +34 -0
- package/extensions/execution-convergence/active-evidence.ts +0 -129
- package/extensions/execution-convergence/index.ts +0 -442
- package/extensions/execution-convergence/workspace-provenance.ts +0 -338
- package/extensions/setup/intercom-fs-helper.cjs +0 -130
- package/extensions/setup/intercom.ts +0 -603
- package/extensions/subagents/src/backends/stub.ts +0 -303
package/bin/openpi.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { createJiti } from "jiti";
|
|
5
|
+
|
|
6
|
+
function printHelp() {
|
|
7
|
+
console.log(`OpenPI Web Workbench
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
openpi web [workspace]
|
|
11
|
+
openpi [workspace] Alias for openpi web [workspace]
|
|
12
|
+
|
|
13
|
+
Options:
|
|
14
|
+
--port <number> Bind a specific loopback port (development)
|
|
15
|
+
--no-open Do not open a browser (development)
|
|
16
|
+
|
|
17
|
+
Starts an isolated local Web runtime. Browser conversations and session changes
|
|
18
|
+
never enter an interactive terminal Pi session.`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const args = process.argv.slice(2);
|
|
22
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
23
|
+
printHelp();
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
const command = args[0] === "web" ? args.slice(1) : args;
|
|
27
|
+
const noOpen = command.includes("--no-open");
|
|
28
|
+
const noWorkspace = command.includes("--no-workspace");
|
|
29
|
+
const portIndex = command.indexOf("--port");
|
|
30
|
+
const portText = portIndex >= 0 ? command[portIndex + 1] : undefined;
|
|
31
|
+
const workspaceArgs = [];
|
|
32
|
+
for (let index = 0; index < command.length; index++) {
|
|
33
|
+
const value = command[index];
|
|
34
|
+
if (value === "--no-open") continue;
|
|
35
|
+
if (value === "--no-workspace") continue;
|
|
36
|
+
if (value === "--port") {
|
|
37
|
+
index++;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
workspaceArgs.push(value);
|
|
41
|
+
}
|
|
42
|
+
const configuredPort = portText ?? process.env.OPENPI_WEB_PORT;
|
|
43
|
+
const port = configuredPort === undefined ? undefined : Number(configuredPort);
|
|
44
|
+
if (portIndex >= 0 && (!portText || portText.startsWith("--"))) {
|
|
45
|
+
console.error("--port requires a value");
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
if (port !== undefined && (!Number.isInteger(port) || port < 0 || port > 65535)) {
|
|
49
|
+
console.error("--port must be an integer between 0 and 65535");
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
if (workspaceArgs.length > 1) {
|
|
53
|
+
console.error("Usage: openpi web [workspace]");
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
if (noWorkspace && workspaceArgs.length > 0) {
|
|
57
|
+
console.error("--no-workspace cannot be combined with a workspace");
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let host;
|
|
62
|
+
let runtime;
|
|
63
|
+
let stopping;
|
|
64
|
+
const stop = () => {
|
|
65
|
+
stopping ??= host?.stop() ?? runtime?.dispose() ?? Promise.resolve();
|
|
66
|
+
return stopping;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const bootstrap = createJiti(import.meta.url);
|
|
71
|
+
const { missingPiCodingAgentDiagnostic, resolveStandaloneJitiAliases } =
|
|
72
|
+
await bootstrap.import("../web/host/pi-coding-agent-entry.ts");
|
|
73
|
+
const aliases = resolveStandaloneJitiAliases({
|
|
74
|
+
fromUrl: import.meta.url,
|
|
75
|
+
});
|
|
76
|
+
if (!aliases["@earendil-works/pi-coding-agent"]) {
|
|
77
|
+
console.error(missingPiCodingAgentDiagnostic());
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
const jiti = createJiti(import.meta.url, { alias: aliases });
|
|
81
|
+
const [browserModule, hostModule, runtimeModule, statusModule, traceModule] =
|
|
82
|
+
await Promise.all([
|
|
83
|
+
jiti.import("../web/host/browser-launcher.ts"),
|
|
84
|
+
jiti.import("../web/host/web-host.ts"),
|
|
85
|
+
jiti.import("../web/runtime/pi-runtime.ts"),
|
|
86
|
+
jiti.import("../web/host/terminal-status.ts"),
|
|
87
|
+
jiti.import("../web/trace.ts"),
|
|
88
|
+
]);
|
|
89
|
+
const { openBrowser } = browserModule;
|
|
90
|
+
const { WebHost } = hostModule;
|
|
91
|
+
const { PiWebRuntime } = runtimeModule;
|
|
92
|
+
const { formatWebReadyScreen } = statusModule;
|
|
93
|
+
const { traceWeb } = traceModule;
|
|
94
|
+
runtime = noWorkspace
|
|
95
|
+
? await PiWebRuntime.createWithoutWorkspace()
|
|
96
|
+
: await PiWebRuntime.create(resolve(workspaceArgs[0] ?? process.cwd()));
|
|
97
|
+
host = new WebHost({
|
|
98
|
+
runtime,
|
|
99
|
+
...(port === undefined ? {} : { port }),
|
|
100
|
+
...(process.env.OPENPI_WEB_TOKEN
|
|
101
|
+
? { token: process.env.OPENPI_WEB_TOKEN }
|
|
102
|
+
: {}),
|
|
103
|
+
...(process.env.OPENPI_WEB_ALLOWED_ORIGIN
|
|
104
|
+
? { allowedOrigins: [process.env.OPENPI_WEB_ALLOWED_ORIGIN] }
|
|
105
|
+
: {}),
|
|
106
|
+
});
|
|
107
|
+
await host.start();
|
|
108
|
+
const onStopSignal = () => {
|
|
109
|
+
void stop().then(
|
|
110
|
+
() => process.exit(0),
|
|
111
|
+
(error) => {
|
|
112
|
+
console.error(
|
|
113
|
+
`Failed to stop OpenPI Web Workbench: ${error instanceof Error ? error.message : String(error)}`,
|
|
114
|
+
);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
120
|
+
process.once(signal, onStopSignal);
|
|
121
|
+
}
|
|
122
|
+
traceWeb("web_started", {
|
|
123
|
+
...(runtime.workspaceSelected === true ? { cwd: runtime.cwd } : {}),
|
|
124
|
+
origin: host.origin,
|
|
125
|
+
});
|
|
126
|
+
const opened = noOpen ? false : await openBrowser(host.url);
|
|
127
|
+
if (noWorkspace) {
|
|
128
|
+
console.log(
|
|
129
|
+
formatWebReadyScreen({
|
|
130
|
+
origin: host.origin,
|
|
131
|
+
url: host.url,
|
|
132
|
+
opened,
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
} else {
|
|
136
|
+
console.log(`OpenPI Web Workbench is running at ${host.origin}`);
|
|
137
|
+
if (!opened) console.log(`Open this URL in a browser: ${host.url}`);
|
|
138
|
+
}
|
|
139
|
+
} catch (error) {
|
|
140
|
+
let cleanupError;
|
|
141
|
+
try {
|
|
142
|
+
await stop();
|
|
143
|
+
} catch (caught) {
|
|
144
|
+
cleanupError = caught;
|
|
145
|
+
}
|
|
146
|
+
console.error(
|
|
147
|
+
`Failed to start OpenPI Web Workbench: ${error instanceof Error ? error.message : String(error)}`,
|
|
148
|
+
);
|
|
149
|
+
if (cleanupError) {
|
|
150
|
+
console.error(
|
|
151
|
+
`Failed to clean up OpenPI Web Workbench: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Mario Zechner
|
|
4
|
+
Copyright (c) 2025-2026 Can Bölük
|
|
5
|
+
Copyright (c) 2026 Stencil Labs, Inc.
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# OAuth model providers
|
|
2
|
+
|
|
3
|
+
This extension registers two opt-in model providers backed by account OAuth:
|
|
4
|
+
|
|
5
|
+
- `google-antigravity` uses Google Cloud Code Assist and supports ordinary Pi
|
|
6
|
+
tool calls.
|
|
7
|
+
- `cursor` uses Cursor AgentService and is experimental, chat-only support.
|
|
8
|
+
It does not advertise or execute Cursor-native coding tools. If the server
|
|
9
|
+
requests one, the request fails explicitly instead of bypassing Pi's tool and
|
|
10
|
+
permission lifecycle.
|
|
11
|
+
|
|
12
|
+
After installing OpenPI, restart Pi or run `/reload`, then authenticate and
|
|
13
|
+
select a model:
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
/login google-antigravity
|
|
17
|
+
/login cursor
|
|
18
|
+
/model
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For source-checkout testing, follow the repository's
|
|
22
|
+
[development runtime provenance procedure](../../README.md#开发运行时区分-npm-与当前源码).
|
|
23
|
+
Remove any previously installed OpenPI source, install the checkout, and verify
|
|
24
|
+
that `pi list` reports this checkout as the only OpenPI source before reloading
|
|
25
|
+
Pi. Do not mix an installed OpenPI package with an explicitly loaded checkout
|
|
26
|
+
extension, because that does not prove which source owns the runtime behavior.
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
pi list
|
|
30
|
+
OLD_OPENPI_SOURCE=/absolute/path/to/old/openpi
|
|
31
|
+
pi remove "$OLD_OPENPI_SOURCE"
|
|
32
|
+
pi install "$PWD"
|
|
33
|
+
pi list
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The model catalog is refreshed from the authenticated account and persisted by
|
|
37
|
+
Pi. A failed refresh retains the last successful account catalog; Antigravity
|
|
38
|
+
also has a validated static baseline, while Cursor keeps the server-side `Auto`
|
|
39
|
+
route as its baseline. No provider is contacted until it is selected.
|
|
40
|
+
|
|
41
|
+
Pi's interactive clipboard flow inserts an image's local path into the editor.
|
|
42
|
+
When Cursor is selected, a supported PNG/JPEG/GIF/WebP path at the start of an
|
|
43
|
+
interactive prompt is converted into an actual image attachment (up to 10 MiB)
|
|
44
|
+
before the request is sent. The absolute path is not exposed to the model.
|
|
45
|
+
|
|
46
|
+
The provider also adds an explicit chat-only rule so the normal Pi coding
|
|
47
|
+
system prompt cannot cause Cursor to attempt unavailable read or shell tools.
|
|
48
|
+
|
|
49
|
+
Cursor's token delta describes generated output only, so the provider does not
|
|
50
|
+
publish it as complete context usage. Pi 0.84.3+ can estimate an all-Cursor
|
|
51
|
+
history and trigger threshold compaction without provider usage. Older Pi hosts
|
|
52
|
+
retain the correct unknown-usage state but cannot automatically threshold-
|
|
53
|
+
compact a session with no usage-backed response. Project-wide Pi baseline
|
|
54
|
+
tracking is kept in [#328](https://github.com/openpi-dev/openpi/issues/328).
|
|
55
|
+
|
|
56
|
+
Cursor model discovery and chat use HTTP/2. They honor `PI_PROXY_CURSOR`, then
|
|
57
|
+
`PI_PROXY`, the standard `HTTPS_PROXY`/`HTTP_PROXY` variables, and `ALL_PROXY`;
|
|
58
|
+
`NO_PROXY` bypass rules apply. The proxy must support HTTP CONNECT and preserve
|
|
59
|
+
HTTP/2 ALPN negotiation to Cursor.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential codec for the Antigravity provider.
|
|
3
|
+
*
|
|
4
|
+
* pi persists whatever object `login` returns into auth.json and hands it back
|
|
5
|
+
* to `refreshToken`/`getApiKey` verbatim, so the Antigravity-specific extras
|
|
6
|
+
* (projectId, email) ride along as additional fields. `getApiKey` then packs
|
|
7
|
+
* everything the stream needs into the single `apiKey` string pi threads into
|
|
8
|
+
* `SimpleStreamOptions.apiKey`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { OAuthCredentials } from "@earendil-works/pi-ai/compat";
|
|
12
|
+
|
|
13
|
+
/** Stored credential shape: pi's OAuth fields plus Antigravity extras. */
|
|
14
|
+
export interface AntigravityCredentials extends OAuthCredentials {
|
|
15
|
+
/** Cloud Code Assist project resolved during login provisioning. */
|
|
16
|
+
projectId?: string;
|
|
17
|
+
/** Google account email, best-effort display metadata. */
|
|
18
|
+
email?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** What the stream function needs, packed into the apiKey string. */
|
|
22
|
+
export interface AntigravityApiKeyPayload {
|
|
23
|
+
token: string;
|
|
24
|
+
projectId?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function encodeApiKey(credentials: AntigravityCredentials): string {
|
|
28
|
+
const payload: AntigravityApiKeyPayload = {
|
|
29
|
+
token: credentials.access,
|
|
30
|
+
projectId: credentials.projectId,
|
|
31
|
+
};
|
|
32
|
+
return JSON.stringify(payload);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function decodeApiKey(raw: string): AntigravityApiKeyPayload {
|
|
36
|
+
try {
|
|
37
|
+
const parsed: unknown = JSON.parse(raw);
|
|
38
|
+
if (parsed !== null && typeof parsed === "object" && "token" in parsed) {
|
|
39
|
+
const token = parsed.token;
|
|
40
|
+
if (typeof token === "string") {
|
|
41
|
+
const projectId =
|
|
42
|
+
"projectId" in parsed && typeof parsed.projectId === "string"
|
|
43
|
+
? parsed.projectId
|
|
44
|
+
: undefined;
|
|
45
|
+
return { token, projectId };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
// Not JSON: tolerate a bare access token (e.g. hand-written auth.json).
|
|
50
|
+
}
|
|
51
|
+
return { token: raw };
|
|
52
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Antigravity model discovery.
|
|
3
|
+
*
|
|
4
|
+
* `POST /v1internal:fetchAvailableModels` returns a map of wire model id to
|
|
5
|
+
* metadata (reference: omp packages/catalog/src/discovery/antigravity.ts).
|
|
6
|
+
* Static models are the provider baseline. Network/protocol failures throw so
|
|
7
|
+
* createProvider can retain the last successfully persisted dynamic catalog.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { RefreshModelsContext } from "@earendil-works/pi-ai";
|
|
11
|
+
import { Type, type Static } from "typebox";
|
|
12
|
+
import { Value } from "typebox/value";
|
|
13
|
+
import { ensureAntigravityVersion, getAntigravityUserAgent } from "./oauth.ts";
|
|
14
|
+
import {
|
|
15
|
+
ANTIGRAVITY_API_URL,
|
|
16
|
+
type AntigravityProviderModel,
|
|
17
|
+
} from "./models.ts";
|
|
18
|
+
import {
|
|
19
|
+
collapseAntigravityModels,
|
|
20
|
+
type AntigravityModelDefinition,
|
|
21
|
+
} from "./routing.ts";
|
|
22
|
+
|
|
23
|
+
const DISCOVERY_ENDPOINTS = [
|
|
24
|
+
"https://daily-cloudcode-pa.googleapis.com",
|
|
25
|
+
"https://daily-cloudcode-pa.sandbox.googleapis.com",
|
|
26
|
+
] as const;
|
|
27
|
+
const FETCH_AVAILABLE_MODELS_PATH = "/v1internal:fetchAvailableModels";
|
|
28
|
+
|
|
29
|
+
// Reference: omp ANTIGRAVITY_DISCOVERY_DENYLIST.
|
|
30
|
+
const DISCOVERY_DENYLIST: Record<string, true> = {
|
|
31
|
+
chat_20706: true,
|
|
32
|
+
chat_23310: true,
|
|
33
|
+
"gemini-2.5-pro": true,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const DEFAULT_CONTEXT_WINDOW = 200_000;
|
|
37
|
+
const DEFAULT_MAX_TOKENS = 64_000;
|
|
38
|
+
|
|
39
|
+
const DiscoveryModelSchema = Type.Object({
|
|
40
|
+
displayName: Type.Optional(Type.String()),
|
|
41
|
+
supportsImages: Type.Optional(Type.Boolean()),
|
|
42
|
+
supportsThinking: Type.Optional(Type.Boolean()),
|
|
43
|
+
maxTokens: Type.Optional(Type.Number()),
|
|
44
|
+
maxOutputTokens: Type.Optional(Type.Number()),
|
|
45
|
+
isInternal: Type.Optional(Type.Boolean()),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const DiscoveryResponseSchema = Type.Object({
|
|
49
|
+
models: Type.Optional(Type.Record(Type.String(), DiscoveryModelSchema)),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
type DiscoveryResponse = Static<typeof DiscoveryResponseSchema>;
|
|
53
|
+
|
|
54
|
+
type DiscoveredModel = AntigravityProviderModel & AntigravityModelDefinition;
|
|
55
|
+
|
|
56
|
+
function positiveNumber(value: number | undefined, fallback: number): number {
|
|
57
|
+
return value !== undefined && Number.isFinite(value) && value > 0
|
|
58
|
+
? value
|
|
59
|
+
: fallback;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function toModelDefinition(
|
|
63
|
+
id: string,
|
|
64
|
+
meta: Static<typeof DiscoveryModelSchema>,
|
|
65
|
+
): DiscoveredModel {
|
|
66
|
+
const advertisedMaxTokens = positiveNumber(
|
|
67
|
+
meta.maxOutputTokens,
|
|
68
|
+
DEFAULT_MAX_TOKENS,
|
|
69
|
+
);
|
|
70
|
+
return {
|
|
71
|
+
id,
|
|
72
|
+
name: meta.displayName ?? id,
|
|
73
|
+
api: "antigravity-cloudcode",
|
|
74
|
+
provider: "google-antigravity",
|
|
75
|
+
baseUrl: ANTIGRAVITY_API_URL,
|
|
76
|
+
reasoning: meta.supportsThinking === true,
|
|
77
|
+
input: meta.supportsImages === true ? ["text", "image"] : ["text"],
|
|
78
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
79
|
+
contextWindow: positiveNumber(meta.maxTokens, DEFAULT_CONTEXT_WINDOW),
|
|
80
|
+
maxTokens: id.toLowerCase().includes("claude")
|
|
81
|
+
? Math.min(advertisedMaxTokens, DEFAULT_MAX_TOKENS)
|
|
82
|
+
: advertisedMaxTokens,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function fetchAntigravityModels(
|
|
87
|
+
context: RefreshModelsContext,
|
|
88
|
+
): Promise<DiscoveredModel[]> {
|
|
89
|
+
if (!context.allowNetwork) return [];
|
|
90
|
+
context.signal.throwIfAborted();
|
|
91
|
+
const credential = context.credential;
|
|
92
|
+
if (!credential || credential.type !== "oauth") return [];
|
|
93
|
+
await ensureAntigravityVersion(context.signal);
|
|
94
|
+
|
|
95
|
+
for (const endpoint of DISCOVERY_ENDPOINTS) {
|
|
96
|
+
if (context.signal.aborted) break;
|
|
97
|
+
try {
|
|
98
|
+
const response = await fetch(
|
|
99
|
+
`${endpoint}${FETCH_AVAILABLE_MODELS_PATH}`,
|
|
100
|
+
{
|
|
101
|
+
method: "POST",
|
|
102
|
+
headers: {
|
|
103
|
+
Authorization: `Bearer ${credential.access}`,
|
|
104
|
+
"Content-Type": "application/json",
|
|
105
|
+
"User-Agent": getAntigravityUserAgent(),
|
|
106
|
+
},
|
|
107
|
+
body: "{}",
|
|
108
|
+
signal: context.signal,
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
if (!response.ok) continue;
|
|
112
|
+
const parsed = Value.Parse(
|
|
113
|
+
DiscoveryResponseSchema,
|
|
114
|
+
await response.json(),
|
|
115
|
+
) as DiscoveryResponse;
|
|
116
|
+
if (!parsed.models) continue;
|
|
117
|
+
const discovered = Object.entries(parsed.models)
|
|
118
|
+
.filter(
|
|
119
|
+
([id, meta]) =>
|
|
120
|
+
!Object.hasOwn(DISCOVERY_DENYLIST, id) && meta.isInternal !== true,
|
|
121
|
+
)
|
|
122
|
+
.map(([id, meta]) => toModelDefinition(id, meta));
|
|
123
|
+
return collapseAntigravityModels(discovered);
|
|
124
|
+
} catch {
|
|
125
|
+
// Try the next endpoint; total failure must preserve the stored catalog.
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
context.signal.throwIfAborted();
|
|
129
|
+
throw new Error("Antigravity model discovery failed on all endpoints");
|
|
130
|
+
}
|