shariq-pi-extensions 0.1.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.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +94 -0
  3. package/THIRD_PARTY_NOTICES.md +11 -0
  4. package/docs/ARCHITECTURE.md +54 -0
  5. package/docs/DEVELOPMENT.md +88 -0
  6. package/docs/EXTENSIONS.md +89 -0
  7. package/extensions/antigravity-provider/LICENSE +21 -0
  8. package/extensions/antigravity-provider/README.md +23 -0
  9. package/extensions/antigravity-provider/antigravity/cloud-code-assist.ts +519 -0
  10. package/extensions/antigravity-provider/antigravity/index.ts +67 -0
  11. package/extensions/antigravity-provider/antigravity/models.ts +184 -0
  12. package/extensions/antigravity-provider/antigravity/oauth.ts +499 -0
  13. package/extensions/antigravity-provider/index.ts +3 -0
  14. package/extensions/ask-user/README.md +21 -0
  15. package/extensions/ask-user/index.ts +417 -0
  16. package/extensions/background-terminals/README.md +43 -0
  17. package/extensions/background-terminals/index.ts +400 -0
  18. package/extensions/background-terminals/src/manager.ts +480 -0
  19. package/extensions/background-terminals/src/output-buffer.ts +110 -0
  20. package/extensions/background-terminals/src/presentation.ts +74 -0
  21. package/extensions/background-terminals/src/types.ts +47 -0
  22. package/extensions/background-terminals/src/ui.ts +478 -0
  23. package/extensions/context-usage/LICENSE +21 -0
  24. package/extensions/context-usage/README.md +20 -0
  25. package/extensions/context-usage/index.ts +2006 -0
  26. package/extensions/context-usage/lib/ansi.ts +60 -0
  27. package/extensions/context-usage/lib/boundary.ts +27 -0
  28. package/extensions/context-usage/lib/chat.ts +190 -0
  29. package/extensions/context-usage/lib/config.ts +30 -0
  30. package/extensions/context-usage/lib/fmt.ts +59 -0
  31. package/extensions/context-usage/lib/style.ts +260 -0
  32. package/extensions/copy-all/README.md +15 -0
  33. package/extensions/copy-all/index.ts +72 -0
  34. package/extensions/factory-provider/API_KEYS.md +17 -0
  35. package/extensions/factory-provider/AUTH.md +15 -0
  36. package/extensions/factory-provider/README.md +90 -0
  37. package/extensions/factory-provider/REFRESH_MODELS.md +185 -0
  38. package/extensions/factory-provider/UPDATING.md +97 -0
  39. package/extensions/factory-provider/factory/api-keys.ts +407 -0
  40. package/extensions/factory-provider/factory/auth.ts +132 -0
  41. package/extensions/factory-provider/factory/constants.ts +15 -0
  42. package/extensions/factory-provider/factory/dashboard.ts +363 -0
  43. package/extensions/factory-provider/factory/droid.ts +84 -0
  44. package/extensions/factory-provider/factory/limits.ts +342 -0
  45. package/extensions/factory-provider/factory/models.ts +283 -0
  46. package/extensions/factory-provider/factory/responses.ts +331 -0
  47. package/extensions/factory-provider/factory/warm-theme.ts +53 -0
  48. package/extensions/factory-provider/factory/websocket.ts +130 -0
  49. package/extensions/factory-provider/index.ts +204 -0
  50. package/extensions/factory-provider/scripts/audit-reasoning-efforts.py +84 -0
  51. package/extensions/firecrawl-web/README.md +31 -0
  52. package/extensions/firecrawl-web/auth.ts +92 -0
  53. package/extensions/firecrawl-web/client.ts +238 -0
  54. package/extensions/firecrawl-web/index.ts +96 -0
  55. package/extensions/firecrawl-web/output.ts +125 -0
  56. package/extensions/git-info/README.md +25 -0
  57. package/extensions/git-info/index.ts +162 -0
  58. package/extensions/git-info/src/git.ts +206 -0
  59. package/extensions/git-info/src/ui.ts +291 -0
  60. package/extensions/goal/README.md +44 -0
  61. package/extensions/goal/constants.ts +4 -0
  62. package/extensions/goal/extension.ts +1064 -0
  63. package/extensions/goal/index.ts +2 -0
  64. package/extensions/goal/types.ts +56 -0
  65. package/extensions/goal/ui.ts +334 -0
  66. package/extensions/orchestration/README.md +58 -0
  67. package/extensions/orchestration/dashboard.ts +192 -0
  68. package/extensions/orchestration/engine.ts +706 -0
  69. package/extensions/orchestration/index.ts +243 -0
  70. package/extensions/orchestration/prompts.ts +124 -0
  71. package/extensions/orchestration/protocol.ts +116 -0
  72. package/extensions/orchestration/settings-ui.ts +41 -0
  73. package/extensions/orchestration/settings.ts +59 -0
  74. package/extensions/orchestration/storage.ts +49 -0
  75. package/extensions/orchestration/types.ts +105 -0
  76. package/extensions/performance-status/README.md +5 -0
  77. package/extensions/performance-status/index.ts +171 -0
  78. package/extensions/pi-memory/README.md +53 -0
  79. package/extensions/pi-memory/index.ts +326 -0
  80. package/extensions/pi-memory/src/config.ts +75 -0
  81. package/extensions/pi-memory/src/database.ts +617 -0
  82. package/extensions/pi-memory/src/extraction.ts +178 -0
  83. package/extensions/pi-memory/src/project.ts +80 -0
  84. package/extensions/pi-memory/src/projection.ts +59 -0
  85. package/extensions/pi-memory/src/redaction.ts +60 -0
  86. package/extensions/pi-memory/src/retrieval.ts +30 -0
  87. package/extensions/pi-memory/src/service.ts +194 -0
  88. package/extensions/pi-memory/src/session.ts +146 -0
  89. package/extensions/pi-memory/src/types.ts +82 -0
  90. package/extensions/shared/README.md +18 -0
  91. package/extensions/shared/activity-status.ts +31 -0
  92. package/extensions/shared/child-session.ts +150 -0
  93. package/extensions/shared/context-utilization.ts +47 -0
  94. package/extensions/shared/dashboard-state.ts +99 -0
  95. package/extensions/shared/tool-call-timeout.ts +104 -0
  96. package/extensions/shared/tui-dashboard.ts +139 -0
  97. package/extensions/shell-shortcuts/README.md +17 -0
  98. package/extensions/shell-shortcuts/features/exit-alias.ts +10 -0
  99. package/extensions/shell-shortcuts/index.ts +6 -0
  100. package/extensions/subagents/README.md +89 -0
  101. package/extensions/subagents/index.ts +1386 -0
  102. package/extensions/subagents/src/backend.ts +69 -0
  103. package/extensions/subagents/src/backends/pi.ts +781 -0
  104. package/extensions/subagents/src/catalog.ts +85 -0
  105. package/extensions/subagents/src/config.ts +241 -0
  106. package/extensions/subagents/src/context.ts +57 -0
  107. package/extensions/subagents/src/coordinator.ts +64 -0
  108. package/extensions/subagents/src/domain.ts +294 -0
  109. package/extensions/subagents/src/format.ts +74 -0
  110. package/extensions/subagents/src/manager.ts +813 -0
  111. package/extensions/subagents/src/prompt.ts +94 -0
  112. package/extensions/subagents/src/result-delivery.ts +20 -0
  113. package/extensions/subagents/src/runtime.ts +48 -0
  114. package/extensions/subagents/src/ui/takeover.ts +695 -0
  115. package/extensions/subagents/src/ui/transcript.ts +182 -0
  116. package/extensions/subagents/src/worktree.ts +175 -0
  117. package/extensions/web-fetch/README.md +13 -0
  118. package/extensions/web-fetch/index.ts +441 -0
  119. package/package.json +117 -0
  120. package/skills/background-terminals/SKILL.md +42 -0
  121. package/skills/orchestration/SKILL.md +33 -0
  122. package/skills/subagents/SKILL.md +89 -0
  123. package/themes/ember-warm-dark.json +85 -0
@@ -0,0 +1,184 @@
1
+ import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
2
+
3
+ export const PROVIDER_ID = "antigravity";
4
+ export const PROVIDER_NAME = "Antigravity";
5
+
6
+ export type AntigravityRouting = {
7
+ off?: string;
8
+ routing?: Partial<Record<"off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max", string>>;
9
+ defaultRequestId?: string;
10
+ };
11
+
12
+ // Public Pi model IDs route to the effort-specific runtime IDs reported by `agy models`.
13
+ export const ANTIGRAVITY_ROUTING: Record<string, AntigravityRouting> = {
14
+ "claude-opus-4-6": {
15
+ off: "claude-opus-4-6-thinking",
16
+ routing: {
17
+ minimal: "claude-opus-4-6-thinking",
18
+ low: "claude-opus-4-6-thinking",
19
+ medium: "claude-opus-4-6-thinking",
20
+ high: "claude-opus-4-6-thinking",
21
+ xhigh: "claude-opus-4-6-thinking",
22
+ max: "claude-opus-4-6-thinking",
23
+ },
24
+ defaultRequestId: "claude-opus-4-6-thinking",
25
+ },
26
+ "claude-sonnet-4-6": {
27
+ off: "claude-sonnet-4-6",
28
+ routing: {
29
+ minimal: "claude-sonnet-4-6",
30
+ low: "claude-sonnet-4-6",
31
+ medium: "claude-sonnet-4-6",
32
+ high: "claude-sonnet-4-6",
33
+ xhigh: "claude-sonnet-4-6",
34
+ max: "claude-sonnet-4-6",
35
+ },
36
+ defaultRequestId: "claude-sonnet-4-6",
37
+ },
38
+ "gemini-3.1-pro": {
39
+ off: "gemini-3.1-pro-low",
40
+ routing: {
41
+ minimal: "gemini-3.1-pro-low",
42
+ low: "gemini-3.1-pro-low",
43
+ medium: "gemini-3.1-pro-low",
44
+ high: "gemini-pro-agent",
45
+ xhigh: "gemini-pro-agent",
46
+ max: "gemini-pro-agent",
47
+ },
48
+ defaultRequestId: "gemini-3.1-pro-low",
49
+ },
50
+ "gemini-3.7-flash": {
51
+ off: "gemini-3.7-flash-low",
52
+ routing: {
53
+ minimal: "gemini-3.7-flash-low",
54
+ low: "gemini-3.7-flash-low",
55
+ medium: "gemini-3.7-flash-medium",
56
+ high: "gemini-3.7-flash-high",
57
+ xhigh: "gemini-3.7-flash-high",
58
+ max: "gemini-3.7-flash-high",
59
+ },
60
+ defaultRequestId: "gemini-3.7-flash-low",
61
+ },
62
+ "gemini-3.6-flash": {
63
+ off: "gemini-3.6-flash-low",
64
+ routing: {
65
+ minimal: "gemini-3.6-flash-low",
66
+ low: "gemini-3.6-flash-low",
67
+ medium: "gemini-3.6-flash-medium",
68
+ high: "gemini-3.6-flash-high",
69
+ xhigh: "gemini-3.6-flash-high",
70
+ max: "gemini-3.6-flash-high",
71
+ },
72
+ defaultRequestId: "gemini-3.6-flash-low",
73
+ },
74
+ "gemini-3.5-flash": {
75
+ off: "gemini-3.5-flash-extra-low",
76
+ routing: {
77
+ minimal: "gemini-3.5-flash-extra-low",
78
+ low: "gemini-3.5-flash-extra-low",
79
+ medium: "gemini-3.5-flash-low",
80
+ high: "gemini-3-flash-agent",
81
+ xhigh: "gemini-3-flash-agent",
82
+ max: "gemini-3-flash-agent",
83
+ },
84
+ defaultRequestId: "gemini-3.5-flash-low",
85
+ },
86
+ "gpt-oss-120b": {
87
+ off: "gpt-oss-120b-medium",
88
+ routing: {
89
+ minimal: "gpt-oss-120b-medium",
90
+ low: "gpt-oss-120b-medium",
91
+ medium: "gpt-oss-120b-medium",
92
+ high: "gpt-oss-120b-medium",
93
+ xhigh: "gpt-oss-120b-medium",
94
+ max: "gpt-oss-120b-medium",
95
+ },
96
+ defaultRequestId: "gpt-oss-120b-medium",
97
+ },
98
+ };
99
+
100
+ export const ANTIGRAVITY_MODELS: ProviderModelConfig[] = [
101
+ {
102
+ id: "claude-opus-4-6",
103
+ name: "Claude Opus 4.6 (Antigravity)",
104
+ reasoning: true,
105
+ thinkingLevelMap: { off: null, minimal: "HIGH", low: "HIGH", medium: "HIGH", high: "HIGH", xhigh: "HIGH", max: "HIGH" } as any,
106
+ input: ["text", "image"],
107
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
108
+ contextWindow: 250000,
109
+ maxTokens: 64000,
110
+ },
111
+ {
112
+ id: "claude-sonnet-4-6",
113
+ name: "Claude Sonnet 4.6 (Antigravity)",
114
+ reasoning: true,
115
+ thinkingLevelMap: { off: null, minimal: "THINKING", low: "THINKING", medium: "THINKING", high: "THINKING", xhigh: "THINKING", max: "THINKING" } as any,
116
+ input: ["text", "image"],
117
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
118
+ contextWindow: 250000,
119
+ maxTokens: 64000,
120
+ },
121
+ {
122
+ id: "gemini-3.1-pro",
123
+ name: "Gemini 3.1 Pro (Antigravity)",
124
+ reasoning: true,
125
+ thinkingLevelMap: { off: null, minimal: "LOW", low: "LOW", medium: "LOW", high: "HIGH", xhigh: "HIGH", max: "HIGH" } as any,
126
+ input: ["text", "image"],
127
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
128
+ contextWindow: 1048576,
129
+ maxTokens: 65535,
130
+ },
131
+ {
132
+ id: "gemini-3.7-flash",
133
+ name: "Gemini 3.7 Flash (Antigravity)",
134
+ reasoning: true,
135
+ thinkingLevelMap: { off: null, minimal: "LOW", low: "LOW", medium: "MEDIUM", high: "HIGH", xhigh: "HIGH", max: "HIGH" } as any,
136
+ input: ["text", "image"],
137
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
138
+ contextWindow: 1048576,
139
+ maxTokens: 65536,
140
+ },
141
+ {
142
+ id: "gemini-3.6-flash",
143
+ name: "Gemini 3.6 Flash (Antigravity)",
144
+ reasoning: true,
145
+ thinkingLevelMap: { off: null, minimal: "LOW", low: "LOW", medium: "MEDIUM", high: "HIGH", xhigh: "HIGH", max: "HIGH" } as any,
146
+ input: ["text", "image"],
147
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
148
+ contextWindow: 1048576,
149
+ maxTokens: 65536,
150
+ },
151
+ {
152
+ id: "gemini-3.5-flash",
153
+ name: "Gemini 3.5 Flash (Antigravity)",
154
+ reasoning: true,
155
+ thinkingLevelMap: { off: null, minimal: "LOW", low: "LOW", medium: "MEDIUM", high: "HIGH", xhigh: "HIGH", max: "HIGH" } as any,
156
+ input: ["text", "image"],
157
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
158
+ contextWindow: 1048576,
159
+ maxTokens: 65536,
160
+ },
161
+ {
162
+ id: "gpt-oss-120b",
163
+ name: "GPT-OSS 120B (Antigravity)",
164
+ reasoning: true,
165
+ thinkingLevelMap: { off: null, minimal: "MEDIUM", low: "MEDIUM", medium: "MEDIUM", high: "MEDIUM", xhigh: "MEDIUM", max: "MEDIUM" } as any,
166
+ input: ["text"],
167
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
168
+ contextWindow: 131072,
169
+ maxTokens: 32768,
170
+ },
171
+ ];
172
+
173
+ export function getAntigravityRequestModelId(modelId: string, effort: string | undefined): string {
174
+ const r = ANTIGRAVITY_ROUTING[modelId];
175
+ if (!r) return modelId;
176
+ if (effort === undefined || effort === "off") {
177
+ return r.off ?? r.routing?.minimal ?? r.routing?.low ?? r.defaultRequestId ?? modelId;
178
+ }
179
+ const effortKey = effort as "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
180
+ if (effortKey === "xhigh" || effortKey === "max") {
181
+ return r.routing?.[effortKey] ?? r.routing?.high ?? r.routing?.low ?? r.routing?.minimal ?? r.off ?? r.defaultRequestId ?? modelId;
182
+ }
183
+ return r.routing?.[effortKey] ?? r.routing?.low ?? r.routing?.minimal ?? r.off ?? r.defaultRequestId ?? modelId;
184
+ }
@@ -0,0 +1,499 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { createServer, type Server } from "node:http";
3
+ import https from "node:https";
4
+
5
+ export type OAuthCredentials = {
6
+ refresh: string;
7
+ access: string;
8
+ expires: number;
9
+ projectId?: string;
10
+ email?: string;
11
+ };
12
+
13
+ export type OAuthCallbacks = {
14
+ onAuth(params: { url: string; instructions?: string }): void;
15
+ onPrompt?(params: { message: string }): Promise<string>;
16
+ onSelect?(params: { message: string; options: { id: string; label: string }[] }): Promise<string | undefined>;
17
+ };
18
+
19
+ export type AntigravityApiKey = {
20
+ token: string;
21
+ projectId: string;
22
+ };
23
+
24
+ export const DEFAULT_ENDPOINT = "https://daily-cloudcode-pa.googleapis.com";
25
+ export const ENDPOINT_FALLBACKS = [
26
+ DEFAULT_ENDPOINT,
27
+ "https://cloudcode-pa.googleapis.com",
28
+ ];
29
+ export const REDIRECT_URI = "http://localhost:51121/oauth-callback";
30
+ export const CALLBACK_TIMEOUT_MS = 5 * 60 * 1_000;
31
+
32
+ export function antigravityEnv(name: string): string | undefined {
33
+ return process.env[`ANTIGRAVITY_${name}`] || process.env[`NOAGY_${name}`];
34
+ }
35
+
36
+ export const CALLBACK_HOST = antigravityEnv("CALLBACK_HOST") || "127.0.0.1";
37
+ export const DEFAULT_PROJECT_ID = antigravityEnv("PROJECT_ID") || stableProjectId(process.cwd());
38
+ export const CLIENT_ID = antigravityEnv("CLIENT_ID") || Buffer.from(
39
+ "MTA3MTAwNjA2MDU5MS10bWhzc2luMmgyMWxjcmUyMzV2dG9sb2poNGc0MDNlc" + "C5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbQ==",
40
+ "base64",
41
+ ).toString("utf8");
42
+ export const CLIENT_SECRET = antigravityEnv("CLIENT_SECRET") || Buffer.from(
43
+ "R09DU1BYLUs1OEZXUjQ" + "4NkxkTEoxbUxCOHNYQzR6NnFEQWY=",
44
+ "base64",
45
+ ).toString("utf8");
46
+ export const SCOPES = [
47
+ "https://www.googleapis.com/auth/cloud-platform",
48
+ "https://www.googleapis.com/auth/userinfo.email",
49
+ "https://www.googleapis.com/auth/userinfo.profile",
50
+ "https://www.googleapis.com/auth/cclog",
51
+ "https://www.googleapis.com/auth/experimentsandconfigs",
52
+ ];
53
+ export const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
54
+ export const TOKEN_URL = "https://oauth2.googleapis.com/token";
55
+
56
+ type MinimalResponse = {
57
+ ok: boolean;
58
+ status: number;
59
+ text(): Promise<string>;
60
+ json(): Promise<unknown>;
61
+ };
62
+
63
+ function postTokenFormIpv4(body: URLSearchParams): Promise<MinimalResponse> {
64
+ const url = new URL(TOKEN_URL);
65
+ return new Promise((resolve, reject) => {
66
+ const req = https.request({
67
+ host: url.hostname,
68
+ path: `${url.pathname}${url.search}`,
69
+ method: "POST",
70
+ family: 4,
71
+ timeout: 30_000,
72
+ headers: {
73
+ "Content-Type": "application/x-www-form-urlencoded",
74
+ "Content-Length": Buffer.byteLength(body.toString()),
75
+ },
76
+ }, (res) => {
77
+ const chunks: Buffer[] = [];
78
+ res.on("data", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
79
+ res.on("end", () => {
80
+ const responseText = Buffer.concat(chunks).toString("utf8");
81
+ resolve({
82
+ ok: (res.statusCode ?? 0) >= 200 && (res.statusCode ?? 0) < 300,
83
+ status: res.statusCode ?? 0,
84
+ text: async () => responseText,
85
+ json: async () => JSON.parse(responseText),
86
+ });
87
+ });
88
+ });
89
+ req.on("timeout", () => req.destroy(new Error("Token request timed out")));
90
+ req.on("error", reject);
91
+ req.end(body.toString());
92
+ });
93
+ }
94
+
95
+ // Shared diagnostics variables
96
+ export let lastStatus: number | undefined;
97
+ export function setLastStatus(status: number | undefined) { lastStatus = status; }
98
+
99
+ export let lastEndpoint: string | undefined;
100
+ export function setLastEndpoint(endpoint: string | undefined) { lastEndpoint = endpoint; }
101
+
102
+ export let lastError: string | undefined;
103
+ export function setLastError(error: string | undefined) { lastError = error; }
104
+
105
+ export let lastProjectId: string | undefined;
106
+ export function setLastProjectId(projectId: string | undefined) { lastProjectId = projectId; }
107
+
108
+ export let lastResolvedRuntimeModel: string | undefined;
109
+ export function setLastResolvedRuntimeModel(model: string | undefined) { lastResolvedRuntimeModel = model; }
110
+
111
+ export let lastAvailableModels: string | undefined;
112
+ export function setLastAvailableModels(models: string | undefined) { lastAvailableModels = models; }
113
+
114
+ export let lastMatchedModelDebug: string | undefined;
115
+ export function setLastMatchedModelDebug(debug: string | undefined) { lastMatchedModelDebug = debug; }
116
+
117
+ export function nowRequestId(): string {
118
+ return `antigravity-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
119
+ }
120
+
121
+ export function stableProjectId(seed: string): string {
122
+ const bytes = createHash("sha1").update(`antigravity:${seed}`).digest().subarray(0, 16);
123
+ bytes[6] = (bytes[6] & 0x0f) | 0x50;
124
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
125
+ const hex = bytes.toString("hex");
126
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
127
+ }
128
+
129
+ function base64Url(buffer: Buffer): string {
130
+ return buffer.toString("base64url");
131
+ }
132
+
133
+ function generatePKCE(): { verifier: string; challenge: string } {
134
+ const verifier = base64Url(randomBytes(32));
135
+ const challenge = base64Url(createHash("sha256").update(verifier).digest());
136
+ return { verifier, challenge };
137
+ }
138
+
139
+ export function endpointCandidates(): string[] {
140
+ const explicit = antigravityEnv("BASE_URL")?.trim();
141
+ return explicit ? [explicit] : ENDPOINT_FALLBACKS;
142
+ }
143
+
144
+ export function safeError(error: unknown): string {
145
+ return error instanceof Error ? error.message : String(error);
146
+ }
147
+
148
+ export function sanitizeText(text: unknown): string {
149
+ return Array.from(String(text ?? ""), (character) => {
150
+ const code = character.charCodeAt(0);
151
+ return character.length === 1 && code >= 0xd800 && code <= 0xdfff ? "\uFFFD" : character;
152
+ }).join("");
153
+ }
154
+
155
+ function boundedFetchSignal(signal?: AbortSignal, timeoutMs = 10_000): AbortSignal {
156
+ const timeout = AbortSignal.timeout(timeoutMs);
157
+ return signal ? AbortSignal.any([signal, timeout]) : timeout;
158
+ }
159
+
160
+ export function parseApiKey(apiKeyRaw: string | undefined): AntigravityApiKey {
161
+ if (!apiKeyRaw) throw new Error("No Antigravity credentials. Run /login antigravity.");
162
+ try {
163
+ const parsed = JSON.parse(apiKeyRaw) as Partial<AntigravityApiKey>;
164
+ if (!parsed.token || !parsed.projectId) throw new Error("missing token or projectId");
165
+ return { token: parsed.token, projectId: parsed.projectId };
166
+ } catch (error) {
167
+ throw new Error(`Invalid Antigravity credentials. Run /login antigravity. (${safeError(error)})`);
168
+ }
169
+ }
170
+
171
+ export function antigravityHeaders(token: string): Record<string, string> {
172
+ const platform = process.platform === "win32" ? "windows" : process.platform;
173
+ const arch = process.arch === "x64" ? "amd64" : process.arch === "ia32" ? "386" : process.arch;
174
+ return {
175
+ Authorization: `Bearer ${token}`,
176
+ "Content-Type": "application/json",
177
+ Accept: "text/event-stream",
178
+ "Accept-Encoding": "gzip",
179
+ "User-Agent": antigravityEnv("USER_AGENT") || `antigravity/cli/1.1.13 (aidev_client; os_type=${platform}; arch=${arch}; cl=964361259; auth_method=consumer)`,
180
+ };
181
+ }
182
+
183
+ export function jsonOrTextError(text: string): string {
184
+ try {
185
+ const parsed = JSON.parse(text) as { error?: { message?: string; status?: string; code?: number } };
186
+ if (parsed.error?.message) return parsed.error.message;
187
+ } catch {
188
+ // not JSON
189
+ }
190
+ return text;
191
+ }
192
+
193
+ async function getUserEmail(token: string, signal?: AbortSignal): Promise<string | undefined> {
194
+ try {
195
+ const res = await fetch("https://www.googleapis.com/oauth2/v1/userinfo?alt=json", {
196
+ headers: { Authorization: `Bearer ${token}` },
197
+ signal: boundedFetchSignal(signal),
198
+ });
199
+ if (!res.ok) return undefined;
200
+ const data = (await res.json()) as { email?: string };
201
+ return data.email;
202
+ } catch {
203
+ return undefined;
204
+ }
205
+ }
206
+
207
+ export function extractProjectId(data: any): string | undefined {
208
+ if (!data || typeof data !== "object") return undefined;
209
+ const direct = data.antigravityProjectId ?? data.projectId ?? data.backendProjectId ?? data.userDefinedCloudaicompanionProject ?? data.cloudaicompanionProject ?? data.project;
210
+ if (typeof direct === "string" && direct) return direct;
211
+ if (direct && typeof direct === "object" && typeof direct.id === "string" && direct.id) return direct.id;
212
+ for (const key of ["projects", "projectIds", "cloudaicompanionProjects"]) {
213
+ const value = data[key];
214
+ if (Array.isArray(value)) {
215
+ for (const item of value) {
216
+ const nested = extractProjectId(item);
217
+ if (nested) return nested;
218
+ if (typeof item === "string" && item) return item;
219
+ }
220
+ }
221
+ }
222
+ return undefined;
223
+ }
224
+
225
+ async function listCloudAICompanionProjects(token: string, signal?: AbortSignal): Promise<string | undefined> {
226
+ for (const endpoint of endpointCandidates()) {
227
+ try {
228
+ const res = await fetch(`${endpoint}/v1internal:listCloudAICompanionProjects`, {
229
+ method: "POST",
230
+ headers: antigravityHeaders(token),
231
+ body: JSON.stringify({}),
232
+ signal: boundedFetchSignal(signal),
233
+ });
234
+ lastStatus = res.status;
235
+ lastEndpoint = endpoint;
236
+ if (!res.ok) continue;
237
+ return extractProjectId(await res.json());
238
+ } catch (error) {
239
+ if (signal?.aborted) throw error;
240
+ lastError = safeError(error);
241
+ }
242
+ }
243
+ return undefined;
244
+ }
245
+
246
+ function collectModelLabels(value: any, out: string[] = []): string[] {
247
+ if (!value || out.length > 50) return out;
248
+ if (typeof value === "string") {
249
+ if (/gemini|claude|gpt-oss/i.test(value)) out.push(value);
250
+ return out;
251
+ }
252
+ if (Array.isArray(value)) {
253
+ for (const item of value) collectModelLabels(item, out);
254
+ return out;
255
+ }
256
+ if (typeof value === "object") {
257
+ for (const key of ["id", "name", "label", "displayName", "model", "modelId"]) collectModelLabels(value[key], out);
258
+ for (const nested of Object.values(value)) {
259
+ if (nested && typeof nested === "object") collectModelLabels(nested, out);
260
+ }
261
+ }
262
+ return out;
263
+ }
264
+
265
+ function summarizeModelCandidate(value: any): string {
266
+ if (!value || typeof value !== "object") return String(value ?? "none");
267
+ const out: Record<string, unknown> = {};
268
+ for (const [key, raw] of Object.entries(value)) {
269
+ if (/token|auth|credential|secret|email/i.test(key)) continue;
270
+ if (raw === null || ["string", "number", "boolean"].includes(typeof raw)) out[key] = raw;
271
+ else if (Array.isArray(raw)) out[key] = `[array:${raw.length}]`;
272
+ else if (typeof raw === "object") out[key] = `{${Object.keys(raw as Record<string, unknown>).slice(0, 12).join(",")}}`;
273
+ }
274
+ return JSON.stringify(out).slice(0, 1200);
275
+ }
276
+
277
+ export type DynamicModelInfo = { id: string; experiments?: string[]; apiProvider?: string; modelProvider?: string };
278
+
279
+ function findDynamicModel(value: any, requestedId: string): DynamicModelInfo | undefined {
280
+ if (!value) return undefined;
281
+
282
+ let targetRegex: RegExp;
283
+ const req = requestedId.toLowerCase();
284
+ if (req === "gemini-3.5-flash-low") targetRegex = /gemini[- ]3\.5[- ]flash \(low\)/i;
285
+ else if (req === "gemini-3.5-flash-medium") targetRegex = /gemini[- ]3\.5[- ]flash \(medium\)/i;
286
+ else if (req === "gemini-3.5-flash-high") targetRegex = /gemini[- ]3\.5[- ]flash \(high\)/i;
287
+ else if (req.includes("claude-opus-4-6")) targetRegex = /claude.*opus.*4\.6/i;
288
+ else if (req.includes("claude-sonnet-4-6")) targetRegex = /claude.*sonnet.*4\.6/i;
289
+ else if (req.includes("gpt-oss-120b")) targetRegex = /gpt.*oss.*120b/i;
290
+ else if (req === "gemini-3.1-pro-low") targetRegex = /gemini[- ]3\.1[- ]pro \(low\)/i;
291
+ else if (req === "gemini-3.1-pro-high") targetRegex = /gemini[- ]3\.1[- ]pro \(high\)/i;
292
+ else targetRegex = new RegExp(req.replace(/-/g, ".*"), "i");
293
+
294
+ if (typeof value === "string") return targetRegex.test(value) ? { id: value } : undefined;
295
+ if (Array.isArray(value)) {
296
+ for (const item of value) {
297
+ const found = findDynamicModel(item, requestedId);
298
+ if (found) return found;
299
+ }
300
+ return undefined;
301
+ }
302
+ if (typeof value === "object") {
303
+ const label = value.label ?? value.displayName ?? value.name ?? value.modelId ?? value.id ?? value.model;
304
+ if (typeof label === "string" && targetRegex.test(label)) {
305
+ lastMatchedModelDebug = summarizeModelCandidate(value);
306
+ return {
307
+ id: String(value.modelId ?? value.id ?? value.model ?? label),
308
+ experiments: value.modelExperiments,
309
+ apiProvider: value.apiProvider,
310
+ modelProvider: value.modelProvider
311
+ };
312
+ }
313
+ for (const nested of Object.values(value)) {
314
+ if (nested && typeof nested === "object") {
315
+ const found = findDynamicModel(nested, requestedId);
316
+ if (found) return found;
317
+ }
318
+ }
319
+ }
320
+ return undefined;
321
+ }
322
+
323
+ export async function fetchAvailableRuntimeModel(token: string, projectId: string, requestedRuntimeModel: string, signal?: AbortSignal): Promise<DynamicModelInfo | undefined> {
324
+ const bodies = [{}, { cloudaicompanionProject: projectId }, { project: projectId }];
325
+ for (const endpoint of endpointCandidates()) {
326
+ for (const candidateBody of bodies) {
327
+ try {
328
+ const res = await fetch(`${endpoint}/v1internal:fetchAvailableModels`, {
329
+ method: "POST",
330
+ headers: antigravityHeaders(token),
331
+ body: JSON.stringify(candidateBody),
332
+ signal: boundedFetchSignal(signal),
333
+ });
334
+ lastStatus = res.status;
335
+ lastEndpoint = endpoint;
336
+ if (!res.ok) continue;
337
+ const data = await res.json();
338
+ const labels = [...new Set(collectModelLabels(data))].slice(0, 12);
339
+ lastAvailableModels = labels.join(",");
340
+ const matched = findDynamicModel(data, requestedRuntimeModel);
341
+ if (matched) return matched;
342
+ } catch (error) {
343
+ if (signal?.aborted) throw error;
344
+ lastError = safeError(error);
345
+ }
346
+ }
347
+ }
348
+ return undefined;
349
+ }
350
+
351
+ export async function loadCodeAssist(token: string, signal?: AbortSignal): Promise<string | undefined> {
352
+ const body = JSON.stringify({
353
+ metadata: { ideType: "ANTIGRAVITY" },
354
+ });
355
+
356
+ for (const endpoint of endpointCandidates()) {
357
+ try {
358
+ const res = await fetch(`${endpoint}/v1internal:loadCodeAssist`, {
359
+ method: "POST",
360
+ headers: antigravityHeaders(token),
361
+ body,
362
+ signal: boundedFetchSignal(signal),
363
+ });
364
+ lastStatus = res.status;
365
+ lastEndpoint = endpoint;
366
+ if (!res.ok) continue;
367
+ const project = extractProjectId(await res.json());
368
+ if (project) return project;
369
+ return await listCloudAICompanionProjects(token, signal);
370
+ } catch (error) {
371
+ if (signal?.aborted) throw error;
372
+ lastError = safeError(error);
373
+ }
374
+ }
375
+ return undefined;
376
+ }
377
+
378
+ type CallbackServer = {
379
+ server: Server;
380
+ waitForCode: () => Promise<{ code: string; state: string }>;
381
+ };
382
+
383
+ function startCallbackServer(): Promise<CallbackServer> {
384
+ return new Promise((resolve, reject) => {
385
+ let resolveCode!: (value: { code: string; state: string }) => void;
386
+ let rejectCode!: (error: Error) => void;
387
+ const codePromise = new Promise<{ code: string; state: string }>((res, rej) => {
388
+ resolveCode = res;
389
+ rejectCode = rej;
390
+ });
391
+
392
+ const server = createServer((req, res) => {
393
+ const url = new URL(req.url || "", REDIRECT_URI);
394
+ if (url.pathname !== "/oauth-callback") {
395
+ res.writeHead(404, { "Content-Type": "text/html; charset=utf-8" });
396
+ res.end("Antigravity callback route not found.");
397
+ return;
398
+ }
399
+
400
+ const error = url.searchParams.get("error");
401
+ const code = url.searchParams.get("code");
402
+ const state = url.searchParams.get("state");
403
+ if (error) {
404
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
405
+ res.end(`Antigravity authentication failed: ${error}`);
406
+ rejectCode(new Error(`OAuth error: ${error}`));
407
+ return;
408
+ }
409
+ if (!code || !state) {
410
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
411
+ res.end("Antigravity authentication failed: missing code or state.");
412
+ rejectCode(new Error("Missing code or state in OAuth callback"));
413
+ return;
414
+ }
415
+
416
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
417
+ res.end("Antigravity authentication complete. You can close this window and return to Pi.");
418
+ resolveCode({ code, state });
419
+ });
420
+
421
+ server.on("error", reject);
422
+ server.listen(51121, CALLBACK_HOST, () => resolve({ server, waitForCode: () => codePromise }));
423
+ });
424
+ }
425
+
426
+ export async function loginAntigravity(callbacks: OAuthCallbacks): Promise<OAuthCredentials> {
427
+ const { verifier, challenge } = generatePKCE();
428
+ const { server, waitForCode } = await startCallbackServer();
429
+ try {
430
+ const authParams = new URLSearchParams({
431
+ client_id: CLIENT_ID,
432
+ response_type: "code",
433
+ redirect_uri: REDIRECT_URI,
434
+ scope: SCOPES.join(" "),
435
+ code_challenge: challenge,
436
+ code_challenge_method: "S256",
437
+ state: verifier,
438
+ access_type: "offline",
439
+ prompt: "consent",
440
+ });
441
+ callbacks.onAuth({ url: `${AUTH_URL}?${authParams.toString()}`, instructions: "Complete Google sign-in. Pi will capture the local callback." });
442
+
443
+ let callbackTimer: ReturnType<typeof setTimeout> | undefined;
444
+ const callbackTimeout = new Promise<never>((_resolve, reject) => {
445
+ callbackTimer = setTimeout(() => reject(new Error("Antigravity authentication callback timed out.")), CALLBACK_TIMEOUT_MS);
446
+ callbackTimer.unref?.();
447
+ });
448
+ const { code, state } = await Promise.race([waitForCode(), callbackTimeout]).finally(() => {
449
+ if (callbackTimer) clearTimeout(callbackTimer);
450
+ });
451
+ if (state !== verifier) throw new Error("OAuth state mismatch");
452
+
453
+ const tokenResponse = await postTokenFormIpv4(new URLSearchParams({
454
+ client_id: CLIENT_ID,
455
+ client_secret: CLIENT_SECRET,
456
+ code,
457
+ grant_type: "authorization_code",
458
+ redirect_uri: REDIRECT_URI,
459
+ code_verifier: verifier,
460
+ }));
461
+ if (!tokenResponse.ok) throw new Error(`Token exchange failed: ${await tokenResponse.text()}`);
462
+ const tokenData = (await tokenResponse.json()) as { access_token: string; refresh_token?: string; expires_in: number };
463
+ if (!tokenData.refresh_token) throw new Error("No refresh token received. Re-run /login antigravity and allow offline access.");
464
+
465
+ const [email, discoveredProject] = await Promise.all([getUserEmail(tokenData.access_token), loadCodeAssist(tokenData.access_token)]);
466
+ return {
467
+ refresh: tokenData.refresh_token,
468
+ access: tokenData.access_token,
469
+ expires: Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000,
470
+ projectId: discoveredProject || DEFAULT_PROJECT_ID,
471
+ email,
472
+ };
473
+ } finally {
474
+ server.close();
475
+ }
476
+ }
477
+
478
+ export async function refreshAntigravityToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {
479
+ const response = await postTokenFormIpv4(new URLSearchParams({
480
+ client_id: CLIENT_ID,
481
+ client_secret: CLIENT_SECRET,
482
+ refresh_token: credentials.refresh,
483
+ grant_type: "refresh_token",
484
+ }));
485
+ if (!response.ok) throw new Error(`Antigravity token refresh failed: ${await response.text()}`);
486
+ const data = (await response.json()) as { access_token: string; expires_in: number; refresh_token?: string };
487
+ const discoveredProject = await loadCodeAssist(data.access_token);
488
+ return {
489
+ ...credentials,
490
+ refresh: data.refresh_token || credentials.refresh,
491
+ access: data.access_token,
492
+ expires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,
493
+ projectId: discoveredProject || credentials.projectId || DEFAULT_PROJECT_ID,
494
+ };
495
+ }
496
+
497
+ export function getApiKey(credentials: OAuthCredentials): string {
498
+ return JSON.stringify({ token: credentials.access, projectId: credentials.projectId || DEFAULT_PROJECT_ID });
499
+ }
@@ -0,0 +1,3 @@
1
+ // Local persistent Antigravity provider for Pi.
2
+ // Provider id intentionally remains "antigravity" so existing auth and model names keep working.
3
+ export { default } from "./antigravity/index.ts";