@f5-sales-demo/pi-ai 20.3.1 → 20.3.3
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5-sales-demo/pi-ai",
|
|
4
|
-
"version": "20.3.
|
|
4
|
+
"version": "20.3.3",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://github.com/f5-sales-demo/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@anthropic-ai/sdk": "^0.115",
|
|
47
47
|
"@aws-sdk/client-bedrock-runtime": "^3",
|
|
48
48
|
"@bufbuild/protobuf": "^2.11",
|
|
49
|
-
"@f5-sales-demo/pi-utils": "20.3.
|
|
49
|
+
"@f5-sales-demo/pi-utils": "20.3.3",
|
|
50
50
|
"@google/genai": "^2.15",
|
|
51
51
|
"@sinclair/typebox": "^0.34",
|
|
52
52
|
"@smithy/node-http-handler": "^4.4",
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* Antigravity OAuth flow (Gemini 3, Claude, GPT-OSS via Google Cloud)
|
|
3
3
|
* Uses different OAuth credentials than google-gemini-cli for access to additional models.
|
|
4
4
|
*/
|
|
5
|
+
import { readFile } from "node:fs/promises";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { $env } from "@f5-sales-demo/pi-utils";
|
|
5
9
|
import { getAntigravityAuthHeaders } from "../../providers/google-gemini-cli";
|
|
6
10
|
import { OAuthCallbackFlow } from "./callback-server";
|
|
7
11
|
import type { OAuthController, OAuthCredentials } from "./types";
|
|
@@ -28,6 +32,9 @@ const CLOUD_CODE_ENDPOINT = "https://cloudcode-pa.googleapis.com";
|
|
|
28
32
|
const TIER_LEGACY = "legacy-tier";
|
|
29
33
|
const PROJECT_ONBOARD_MAX_ATTEMPTS = 5;
|
|
30
34
|
const PROJECT_ONBOARD_INTERVAL_MS = 2000;
|
|
35
|
+
const PROJECT_ID_PATTERN = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/;
|
|
36
|
+
const PROJECT_NUMBER_PATTERN = /^\d{12}$/;
|
|
37
|
+
const ANTIGRAVITY_CLI_TOKEN_PATH = join(homedir(), ".gemini", "antigravity-cli", "antigravity-oauth-token");
|
|
31
38
|
|
|
32
39
|
interface LoadCodeAssistPayload {
|
|
33
40
|
cloudaicompanionProject?: string | { id?: string };
|
|
@@ -48,6 +55,139 @@ export const ANTIGRAVITY_LOAD_CODE_ASSIST_METADATA = Object.freeze({
|
|
|
48
55
|
pluginType: "GEMINI",
|
|
49
56
|
});
|
|
50
57
|
|
|
58
|
+
interface AntigravityProjectRequest {
|
|
59
|
+
cloudaicompanionProject?: string;
|
|
60
|
+
metadata: typeof ANTIGRAVITY_LOAD_CODE_ASSIST_METADATA & { duetProject?: string };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface ProjectEnvironment {
|
|
64
|
+
GOOGLE_CLOUD_PROJECT?: string;
|
|
65
|
+
GOOGLE_CLOUD_PROJECT_ID?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface GcloudProjectCommandResult {
|
|
69
|
+
exitCode: number;
|
|
70
|
+
stdout: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type GcloudProjectCommand = () => Promise<GcloudProjectCommandResult>;
|
|
74
|
+
|
|
75
|
+
export interface AntigravityProjectSources {
|
|
76
|
+
environment?: ProjectEnvironment;
|
|
77
|
+
readAntigravityProjectId?: () => Promise<string | undefined>;
|
|
78
|
+
readGcloudProjectId?: () => Promise<string | undefined>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface AntigravityLoginOptions {
|
|
82
|
+
projectSources?: AntigravityProjectSources;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function normalizeProjectId(value: unknown): string | undefined {
|
|
86
|
+
if (typeof value !== "string") return undefined;
|
|
87
|
+
const projectId = value.trim();
|
|
88
|
+
return PROJECT_ID_PATTERN.test(projectId) || PROJECT_NUMBER_PATTERN.test(projectId) ? projectId : undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function readAntigravityCliProjectId(
|
|
92
|
+
metadataPath = ANTIGRAVITY_CLI_TOKEN_PATH,
|
|
93
|
+
): Promise<string | undefined> {
|
|
94
|
+
try {
|
|
95
|
+
const metadata = JSON.parse(await readFile(metadataPath, "utf8")) as unknown;
|
|
96
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) return undefined;
|
|
97
|
+
return normalizeProjectId((metadata as { project_id?: unknown }).project_id);
|
|
98
|
+
} catch {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function runGcloudProjectCommand(): Promise<GcloudProjectCommandResult> {
|
|
104
|
+
const subprocess = Bun.spawn(["gcloud", "config", "get-value", "project"], {
|
|
105
|
+
stdin: "ignore",
|
|
106
|
+
stdout: "pipe",
|
|
107
|
+
stderr: "ignore",
|
|
108
|
+
});
|
|
109
|
+
const [stdout, exitCode] = await Promise.all([new Response(subprocess.stdout).text(), subprocess.exited]);
|
|
110
|
+
return { exitCode, stdout };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function readGcloudProjectId(
|
|
114
|
+
runCommand: GcloudProjectCommand = runGcloudProjectCommand,
|
|
115
|
+
): Promise<string | undefined> {
|
|
116
|
+
try {
|
|
117
|
+
const result = await runCommand();
|
|
118
|
+
if (result.exitCode !== 0) return undefined;
|
|
119
|
+
return normalizeProjectId(result.stdout);
|
|
120
|
+
} catch {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function readExternalProjectId(source: () => Promise<string | undefined>): Promise<string | undefined> {
|
|
126
|
+
try {
|
|
127
|
+
return normalizeProjectId(await source());
|
|
128
|
+
} catch {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function resolveAntigravityProjectId(
|
|
134
|
+
ctrl: OAuthController,
|
|
135
|
+
sources: AntigravityProjectSources = {},
|
|
136
|
+
): Promise<string | undefined> {
|
|
137
|
+
const environment = sources.environment ?? {
|
|
138
|
+
GOOGLE_CLOUD_PROJECT: $env.GOOGLE_CLOUD_PROJECT,
|
|
139
|
+
GOOGLE_CLOUD_PROJECT_ID: $env.GOOGLE_CLOUD_PROJECT_ID,
|
|
140
|
+
};
|
|
141
|
+
const environmentProjectId =
|
|
142
|
+
normalizeProjectId(environment.GOOGLE_CLOUD_PROJECT) ?? normalizeProjectId(environment.GOOGLE_CLOUD_PROJECT_ID);
|
|
143
|
+
if (environmentProjectId) {
|
|
144
|
+
ctrl.onProgress?.("Using the Google Cloud project configured by the environment...");
|
|
145
|
+
return environmentProjectId;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const antigravityProjectId = await readExternalProjectId(
|
|
149
|
+
sources.readAntigravityProjectId ?? readAntigravityCliProjectId,
|
|
150
|
+
);
|
|
151
|
+
if (antigravityProjectId) {
|
|
152
|
+
ctrl.onProgress?.("Using the Google Cloud project configured by Antigravity CLI...");
|
|
153
|
+
return antigravityProjectId;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const gcloudProjectId = await readExternalProjectId(sources.readGcloudProjectId ?? readGcloudProjectId);
|
|
157
|
+
if (gcloudProjectId) {
|
|
158
|
+
ctrl.onProgress?.("Using the Google Cloud project configured by gcloud...");
|
|
159
|
+
return gcloudProjectId;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!ctrl.onPrompt) return undefined;
|
|
163
|
+
const input = await ctrl.onPrompt({
|
|
164
|
+
message: "Google Cloud project ID (leave blank to use individual-tier discovery):",
|
|
165
|
+
placeholder: "my-enterprise-project",
|
|
166
|
+
allowEmpty: true,
|
|
167
|
+
});
|
|
168
|
+
if (!input.trim()) return undefined;
|
|
169
|
+
const promptedProjectId = normalizeProjectId(input);
|
|
170
|
+
if (!promptedProjectId) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
"Invalid Google Cloud project. Use a 6-30 character lowercase project ID or a 12-digit project number.",
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
return promptedProjectId;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function buildProjectRequest(configuredProjectId: string | undefined): AntigravityProjectRequest {
|
|
179
|
+
if (!configuredProjectId) {
|
|
180
|
+
return { metadata: ANTIGRAVITY_LOAD_CODE_ASSIST_METADATA };
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
cloudaicompanionProject: configuredProjectId,
|
|
184
|
+
metadata: {
|
|
185
|
+
...ANTIGRAVITY_LOAD_CODE_ASSIST_METADATA,
|
|
186
|
+
duetProject: configuredProjectId,
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
51
191
|
function readProjectId(value: string | { id?: string } | undefined): string | undefined {
|
|
52
192
|
if (typeof value === "string" && value.length > 0) {
|
|
53
193
|
return value;
|
|
@@ -72,7 +212,7 @@ function getDefaultTierId(allowedTiers?: Array<{ id?: string; isDefault?: boolea
|
|
|
72
212
|
async function onboardProjectWithRetries(
|
|
73
213
|
endpoint: string,
|
|
74
214
|
headers: Record<string, string>,
|
|
75
|
-
onboardBody: { tierId: string
|
|
215
|
+
onboardBody: { tierId: string } & AntigravityProjectRequest,
|
|
76
216
|
onProgress?: (message: string) => void,
|
|
77
217
|
): Promise<string> {
|
|
78
218
|
for (let attempt = 1; attempt <= PROJECT_ONBOARD_MAX_ATTEMPTS; attempt += 1) {
|
|
@@ -108,7 +248,12 @@ async function onboardProjectWithRetries(
|
|
|
108
248
|
);
|
|
109
249
|
}
|
|
110
250
|
|
|
111
|
-
async function discoverProject(
|
|
251
|
+
async function discoverProject(
|
|
252
|
+
accessToken: string,
|
|
253
|
+
configuredProjectId: string | undefined,
|
|
254
|
+
onProgress?: (message: string) => void,
|
|
255
|
+
): Promise<string> {
|
|
256
|
+
const projectRequest = buildProjectRequest(configuredProjectId);
|
|
112
257
|
const headers = {
|
|
113
258
|
Authorization: `Bearer ${accessToken}`,
|
|
114
259
|
"Content-Type": "application/json",
|
|
@@ -121,9 +266,7 @@ async function discoverProject(accessToken: string, onProgress?: (message: strin
|
|
|
121
266
|
const loadResponse = await fetch(`${endpoint}/v1internal:loadCodeAssist`, {
|
|
122
267
|
method: "POST",
|
|
123
268
|
headers,
|
|
124
|
-
body: JSON.stringify(
|
|
125
|
-
metadata: ANTIGRAVITY_LOAD_CODE_ASSIST_METADATA,
|
|
126
|
-
}),
|
|
269
|
+
body: JSON.stringify(projectRequest),
|
|
127
270
|
});
|
|
128
271
|
|
|
129
272
|
if (!loadResponse.ok) {
|
|
@@ -134,17 +277,17 @@ async function discoverProject(accessToken: string, onProgress?: (message: strin
|
|
|
134
277
|
const loadPayload = (await loadResponse.json()) as LoadCodeAssistPayload;
|
|
135
278
|
const existingProject = readProjectId(loadPayload.cloudaicompanionProject);
|
|
136
279
|
if (existingProject) {
|
|
137
|
-
return existingProject;
|
|
280
|
+
return configuredProjectId ?? existingProject;
|
|
138
281
|
}
|
|
139
282
|
|
|
140
283
|
const tierId = getDefaultTierId(loadPayload.allowedTiers);
|
|
141
284
|
onProgress?.("Provisioning project...");
|
|
142
285
|
const onboardBody = {
|
|
143
286
|
tierId,
|
|
144
|
-
|
|
287
|
+
...projectRequest,
|
|
145
288
|
};
|
|
146
289
|
const provisionedProject = await onboardProjectWithRetries(endpoint, headers, onboardBody, onProgress);
|
|
147
|
-
return provisionedProject;
|
|
290
|
+
return configuredProjectId ?? provisionedProject;
|
|
148
291
|
} catch (error) {
|
|
149
292
|
throw new Error(
|
|
150
293
|
`Could not discover or provision an Antigravity project. ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -169,8 +312,11 @@ async function getUserEmail(accessToken: string): Promise<string | undefined> {
|
|
|
169
312
|
}
|
|
170
313
|
|
|
171
314
|
class AntigravityOAuthFlow extends OAuthCallbackFlow {
|
|
172
|
-
|
|
315
|
+
#configuredProjectId: string | undefined;
|
|
316
|
+
|
|
317
|
+
constructor(ctrl: OAuthController, configuredProjectId: string | undefined) {
|
|
173
318
|
super(ctrl, CALLBACK_PORT, CALLBACK_PATH);
|
|
319
|
+
this.#configuredProjectId = configuredProjectId;
|
|
174
320
|
}
|
|
175
321
|
|
|
176
322
|
async generateAuthUrl(state: string, redirectUri: string): Promise<{ url: string; instructions?: string }> {
|
|
@@ -220,7 +366,7 @@ class AntigravityOAuthFlow extends OAuthCallbackFlow {
|
|
|
220
366
|
|
|
221
367
|
this.ctrl.onProgress?.("Getting user info...");
|
|
222
368
|
const email = await getUserEmail(tokenData.access_token);
|
|
223
|
-
const projectId = await discoverProject(tokenData.access_token, this.ctrl.onProgress);
|
|
369
|
+
const projectId = await discoverProject(tokenData.access_token, this.#configuredProjectId, this.ctrl.onProgress);
|
|
224
370
|
|
|
225
371
|
return {
|
|
226
372
|
refresh: tokenData.refresh_token,
|
|
@@ -235,8 +381,12 @@ class AntigravityOAuthFlow extends OAuthCallbackFlow {
|
|
|
235
381
|
/**
|
|
236
382
|
* Login with Antigravity OAuth
|
|
237
383
|
*/
|
|
238
|
-
export async function loginAntigravity(
|
|
239
|
-
|
|
384
|
+
export async function loginAntigravity(
|
|
385
|
+
ctrl: OAuthController,
|
|
386
|
+
options: AntigravityLoginOptions = {},
|
|
387
|
+
): Promise<OAuthCredentials> {
|
|
388
|
+
const configuredProjectId = await resolveAntigravityProjectId(ctrl, options.projectSources);
|
|
389
|
+
const flow = new AntigravityOAuthFlow(ctrl, configuredProjectId);
|
|
240
390
|
return flow.login();
|
|
241
391
|
}
|
|
242
392
|
|