@f5-sales-demo/pi-ai 20.3.2 → 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,9 @@
|
|
|
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";
|
|
5
8
|
import { $env } from "@f5-sales-demo/pi-utils";
|
|
6
9
|
import { getAntigravityAuthHeaders } from "../../providers/google-gemini-cli";
|
|
7
10
|
import { OAuthCallbackFlow } from "./callback-server";
|
|
@@ -29,6 +32,9 @@ const CLOUD_CODE_ENDPOINT = "https://cloudcode-pa.googleapis.com";
|
|
|
29
32
|
const TIER_LEGACY = "legacy-tier";
|
|
30
33
|
const PROJECT_ONBOARD_MAX_ATTEMPTS = 5;
|
|
31
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");
|
|
32
38
|
|
|
33
39
|
interface LoadCodeAssistPayload {
|
|
34
40
|
cloudaicompanionProject?: string | { id?: string };
|
|
@@ -54,6 +60,121 @@ interface AntigravityProjectRequest {
|
|
|
54
60
|
metadata: typeof ANTIGRAVITY_LOAD_CODE_ASSIST_METADATA & { duetProject?: string };
|
|
55
61
|
}
|
|
56
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
|
+
|
|
57
178
|
function buildProjectRequest(configuredProjectId: string | undefined): AntigravityProjectRequest {
|
|
58
179
|
if (!configuredProjectId) {
|
|
59
180
|
return { metadata: ANTIGRAVITY_LOAD_CODE_ASSIST_METADATA };
|
|
@@ -127,8 +248,11 @@ async function onboardProjectWithRetries(
|
|
|
127
248
|
);
|
|
128
249
|
}
|
|
129
250
|
|
|
130
|
-
async function discoverProject(
|
|
131
|
-
|
|
251
|
+
async function discoverProject(
|
|
252
|
+
accessToken: string,
|
|
253
|
+
configuredProjectId: string | undefined,
|
|
254
|
+
onProgress?: (message: string) => void,
|
|
255
|
+
): Promise<string> {
|
|
132
256
|
const projectRequest = buildProjectRequest(configuredProjectId);
|
|
133
257
|
const headers = {
|
|
134
258
|
Authorization: `Bearer ${accessToken}`,
|
|
@@ -188,8 +312,11 @@ async function getUserEmail(accessToken: string): Promise<string | undefined> {
|
|
|
188
312
|
}
|
|
189
313
|
|
|
190
314
|
class AntigravityOAuthFlow extends OAuthCallbackFlow {
|
|
191
|
-
|
|
315
|
+
#configuredProjectId: string | undefined;
|
|
316
|
+
|
|
317
|
+
constructor(ctrl: OAuthController, configuredProjectId: string | undefined) {
|
|
192
318
|
super(ctrl, CALLBACK_PORT, CALLBACK_PATH);
|
|
319
|
+
this.#configuredProjectId = configuredProjectId;
|
|
193
320
|
}
|
|
194
321
|
|
|
195
322
|
async generateAuthUrl(state: string, redirectUri: string): Promise<{ url: string; instructions?: string }> {
|
|
@@ -239,7 +366,7 @@ class AntigravityOAuthFlow extends OAuthCallbackFlow {
|
|
|
239
366
|
|
|
240
367
|
this.ctrl.onProgress?.("Getting user info...");
|
|
241
368
|
const email = await getUserEmail(tokenData.access_token);
|
|
242
|
-
const projectId = await discoverProject(tokenData.access_token, this.ctrl.onProgress);
|
|
369
|
+
const projectId = await discoverProject(tokenData.access_token, this.#configuredProjectId, this.ctrl.onProgress);
|
|
243
370
|
|
|
244
371
|
return {
|
|
245
372
|
refresh: tokenData.refresh_token,
|
|
@@ -254,8 +381,12 @@ class AntigravityOAuthFlow extends OAuthCallbackFlow {
|
|
|
254
381
|
/**
|
|
255
382
|
* Login with Antigravity OAuth
|
|
256
383
|
*/
|
|
257
|
-
export async function loginAntigravity(
|
|
258
|
-
|
|
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);
|
|
259
390
|
return flow.login();
|
|
260
391
|
}
|
|
261
392
|
|