@f5-sales-demo/pi-ai 20.4.2 → 20.4.4

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.4.2",
4
+ "version": "20.4.4",
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.4.2",
49
+ "@f5-sales-demo/pi-utils": "20.4.4",
50
50
  "@google/genai": "^2.15",
51
51
  "@sinclair/typebox": "^0.34",
52
52
  "@smithy/node-http-handler": "^4.4",
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Google Gemini CLI / Antigravity provider.
3
3
  * Shared implementation for both google-gemini-cli and google-antigravity providers.
4
- * Uses the Cloud Code Assist API endpoint to access Gemini and Claude models.
4
+ * Uses Cloud Code Assist plus explicit enterprise Vertex routes for supported Antigravity models.
5
5
  */
6
6
  import { createHash, randomBytes, randomUUID } from "node:crypto";
7
7
  import { abortableSleep, readSseJson } from "@f5-sales-demo/pi-utils";
@@ -61,7 +61,13 @@ export interface GoogleGeminiCliOptions extends StreamOptions {
61
61
  const DEFAULT_ENDPOINT = "https://cloudcode-pa.googleapis.com";
62
62
  const ANTIGRAVITY_DAILY_ENDPOINT = "https://daily-cloudcode-pa.googleapis.com";
63
63
  const ANTIGRAVITY_SANDBOX_ENDPOINT = "https://daily-cloudcode-pa.sandbox.googleapis.com";
64
+ const ANTIGRAVITY_VERTEX_ENDPOINT = "https://aiplatform.googleapis.com";
64
65
  const ANTIGRAVITY_ENDPOINT_FALLBACKS = [ANTIGRAVITY_DAILY_ENDPOINT, ANTIGRAVITY_SANDBOX_ENDPOINT] as const;
66
+ const ANTIGRAVITY_VERTEX_MODELS: Readonly<Record<string, string>> = Object.freeze({
67
+ "gemini-3.6-flash-high": "gemini-3.6-flash",
68
+ "gemini-3.1-pro-high": "gemini-3.1-pro-preview",
69
+ "gemini-3.1-pro-high-vertex": "gemini-3.1-pro-preview",
70
+ });
65
71
 
66
72
  /**
67
73
  * Build a User-Agent string that identifies as Gemini CLI to unlock higher rate limits.
@@ -421,34 +427,36 @@ interface CloudCodeAssistRequest {
421
427
  requestId?: string;
422
428
  }
423
429
 
424
- interface CloudCodeAssistResponseChunk {
425
- response?: {
426
- candidates?: Array<{
427
- content?: {
428
- role: string;
429
- parts?: Array<{
430
- text?: string;
431
- thought?: boolean;
432
- thoughtSignature?: string;
433
- functionCall?: {
434
- name: string;
435
- args: Record<string, unknown>;
436
- id?: string;
437
- };
438
- }>;
439
- };
440
- finishReason?: string;
441
- }>;
442
- usageMetadata?: {
443
- promptTokenCount?: number;
444
- candidatesTokenCount?: number;
445
- thoughtsTokenCount?: number;
446
- totalTokenCount?: number;
447
- cachedContentTokenCount?: number;
430
+ interface GeminiGenerateContentResponse {
431
+ candidates?: Array<{
432
+ content?: {
433
+ role: string;
434
+ parts?: Array<{
435
+ text?: string;
436
+ thought?: boolean;
437
+ thoughtSignature?: string;
438
+ functionCall?: {
439
+ name: string;
440
+ args: Record<string, unknown>;
441
+ id?: string;
442
+ };
443
+ }>;
448
444
  };
449
- modelVersion?: string;
450
- responseId?: string;
445
+ finishReason?: string;
446
+ }>;
447
+ usageMetadata?: {
448
+ promptTokenCount?: number;
449
+ candidatesTokenCount?: number;
450
+ thoughtsTokenCount?: number;
451
+ totalTokenCount?: number;
452
+ cachedContentTokenCount?: number;
451
453
  };
454
+ modelVersion?: string;
455
+ responseId?: string;
456
+ }
457
+
458
+ interface CloudCodeAssistResponseChunk extends GeminiGenerateContentResponse {
459
+ response?: GeminiGenerateContentResponse;
452
460
  traceId?: string;
453
461
  }
454
462
 
@@ -492,11 +500,22 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
492
500
  const parsedCredentials = parseGeminiCliCredentials(apiKeyRaw);
493
501
  const activeCredentials = await refreshGeminiCliCredentialsIfNeeded(parsedCredentials, isAntigravity);
494
502
  const { accessToken, projectId } = activeCredentials;
503
+ const vertexModelId = isAntigravity ? ANTIGRAVITY_VERTEX_MODELS[model.id] : undefined;
504
+ const serviceName = vertexModelId ? "Vertex AI" : "Cloud Code Assist";
495
505
 
496
506
  const baseUrl = model.baseUrl?.trim();
497
- const endpoints = baseUrl ? [baseUrl] : isAntigravity ? ANTIGRAVITY_ENDPOINT_FALLBACKS : [DEFAULT_ENDPOINT];
498
-
499
- let requestBody = buildRequest(model, context, projectId, options, isAntigravity);
507
+ const endpoints = vertexModelId
508
+ ? [ANTIGRAVITY_VERTEX_ENDPOINT]
509
+ : baseUrl
510
+ ? [baseUrl]
511
+ : isAntigravity
512
+ ? ANTIGRAVITY_ENDPOINT_FALLBACKS
513
+ : [DEFAULT_ENDPOINT];
514
+
515
+ const cloudCodeRequest = buildRequest(model, context, projectId, options, isAntigravity);
516
+ let requestBody: CloudCodeAssistRequest | CloudCodeAssistRequest["request"] = vertexModelId
517
+ ? cloudCodeRequest.request
518
+ : cloudCodeRequest;
500
519
  const replacementPayload = await options?.onPayload?.(requestBody, model);
501
520
  if (replacementPayload !== undefined) {
502
521
  requestBody = replacementPayload as typeof requestBody;
@@ -534,7 +553,9 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
534
553
 
535
554
  try {
536
555
  const endpoint = endpoints[Math.min(attempt, endpoints.length - 1)];
537
- requestUrl = `${endpoint}/v1internal:streamGenerateContent?alt=sse`;
556
+ requestUrl = vertexModelId
557
+ ? `${endpoint}/v1/projects/${encodeURIComponent(projectId)}/locations/global/publishers/google/models/${encodeURIComponent(vertexModelId)}:streamGenerateContent?alt=sse`
558
+ : `${endpoint}/v1internal:streamGenerateContent?alt=sse`;
538
559
  response = await fetch(requestUrl, {
539
560
  method: "POST",
540
561
  headers: requestHeaders,
@@ -552,7 +573,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
552
573
  if (response.status === 429) {
553
574
  if (/quota|exhausted/i.test(errorText)) {
554
575
  throw withHttpStatus(
555
- new Error(`Cloud Code Assist API error (429): ${extractErrorMessage(errorText)}`),
576
+ new Error(`${serviceName} API error (429): ${extractErrorMessage(errorText)}`),
556
577
  429,
557
578
  );
558
579
  }
@@ -590,7 +611,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
590
611
 
591
612
  // Not retryable or budget exceeded
592
613
  throw withHttpStatus(
593
- new Error(`Cloud Code Assist API error (${response.status}): ${extractErrorMessage(errorText)}`),
614
+ new Error(`${serviceName} API error (${response.status}): ${extractErrorMessage(errorText)}`),
594
615
  response.status,
595
616
  );
596
617
  } catch (error) {
@@ -665,8 +686,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
665
686
  activeResponse.body!,
666
687
  options?.signal,
667
688
  )) {
668
- const responseData = chunk.response;
669
- if (!responseData) continue;
689
+ const responseData = chunk.response ?? chunk;
670
690
 
671
691
  const candidate = responseData.candidates?.[0];
672
692
  if (candidate?.content?.parts) {
@@ -878,7 +898,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
878
898
  if (!currentResponse.ok) {
879
899
  const retryErrorText = await currentResponse.text();
880
900
  throw withHttpStatus(
881
- new Error(`Cloud Code Assist API error (${currentResponse.status}): ${retryErrorText}`),
901
+ new Error(`${serviceName} API error (${currentResponse.status}): ${retryErrorText}`),
882
902
  currentResponse.status,
883
903
  );
884
904
  }
@@ -896,7 +916,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
896
916
  }
897
917
 
898
918
  if (!receivedContent) {
899
- throw new Error("Cloud Code Assist API returned an empty response");
919
+ throw new Error(`${serviceName} API returned an empty response`);
900
920
  }
901
921
 
902
922
  if (options?.signal?.aborted) {
@@ -8,6 +8,13 @@ const DEFAULT_ANTIGRAVITY_DISCOVERY_ENDPOINTS = [
8
8
  "https://daily-cloudcode-pa.sandbox.googleapis.com",
9
9
  ] as const;
10
10
  const FETCH_AVAILABLE_MODELS_PATH = "/v1internal:fetchAvailableModels";
11
+ const ANTIGRAVITY_VERTEX_ENDPOINT = "https://aiplatform.googleapis.com";
12
+ const ANTIGRAVITY_VERTEX_VARIANTS: Readonly<Record<string, { id: string; name: string }>> = Object.freeze({
13
+ "gemini-3.1-pro-high": {
14
+ id: "gemini-3.1-pro-high-vertex",
15
+ name: "Gemini 3.1 Pro High (Vertex, Antigravity)",
16
+ },
17
+ });
11
18
 
12
19
  const DEFAULT_CONTEXT_WINDOW = 200_000;
13
20
  const DEFAULT_MAX_TOKENS = 64_000;
@@ -222,12 +229,13 @@ export async function fetchAntigravityDiscoveryModels(
222
229
  }
223
230
 
224
231
  const supportsImages = model.supportsImages === true;
232
+ const vertexVariant = ANTIGRAVITY_VERTEX_VARIANTS[modelId];
225
233
  models.push({
226
- id: modelId,
227
- name: model.displayName ? `${model.displayName} (Antigravity)` : modelId,
234
+ id: vertexVariant?.id ?? modelId,
235
+ name: vertexVariant?.name ?? (model.displayName ? `${model.displayName} (Antigravity)` : modelId),
228
236
  api: "google-gemini-cli",
229
237
  provider: "google-antigravity",
230
- baseUrl: endpoint,
238
+ baseUrl: vertexVariant ? ANTIGRAVITY_VERTEX_ENDPOINT : endpoint,
231
239
  reasoning: model.supportsThinking === true,
232
240
  input: supportsImages ? ["text", "image"] : ["text"],
233
241
  cost: {
@@ -30,6 +30,7 @@ const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
30
30
  const TOKEN_URL = "https://oauth2.googleapis.com/token";
31
31
  const CLOUD_CODE_ENDPOINT = "https://cloudcode-pa.googleapis.com";
32
32
  const TIER_LEGACY = "legacy-tier";
33
+ const TIER_STANDARD = "standard-tier";
33
34
  const PROJECT_ONBOARD_MAX_ATTEMPTS = 5;
34
35
  const PROJECT_ONBOARD_INTERVAL_MS = 2000;
35
36
  const PROJECT_ID_PATTERN = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/;
@@ -248,6 +249,25 @@ async function onboardProjectWithRetries(
248
249
  );
249
250
  }
250
251
 
252
+ async function loadCodeAssist(
253
+ endpoint: string,
254
+ headers: Record<string, string>,
255
+ projectRequest: AntigravityProjectRequest,
256
+ ): Promise<LoadCodeAssistPayload> {
257
+ const response = await fetch(`${endpoint}/v1internal:loadCodeAssist`, {
258
+ method: "POST",
259
+ headers,
260
+ body: JSON.stringify(projectRequest),
261
+ });
262
+
263
+ if (!response.ok) {
264
+ const errorText = await response.text();
265
+ throw new Error(`loadCodeAssist failed: ${response.status} ${response.statusText}: ${errorText}`);
266
+ }
267
+
268
+ return (await response.json()) as LoadCodeAssistPayload;
269
+ }
270
+
251
271
  async function discoverProject(
252
272
  accessToken: string,
253
273
  configuredProjectId: string | undefined,
@@ -263,31 +283,42 @@ async function discoverProject(
263
283
  onProgress?.("Checking for existing project...");
264
284
  const endpoint = CLOUD_CODE_ENDPOINT;
265
285
  try {
266
- const loadResponse = await fetch(`${endpoint}/v1internal:loadCodeAssist`, {
267
- method: "POST",
268
- headers,
269
- body: JSON.stringify(projectRequest),
270
- });
286
+ const loadPayload = await loadCodeAssist(endpoint, headers, projectRequest);
287
+ const existingProject = readProjectId(loadPayload.cloudaicompanionProject);
271
288
 
272
- if (!loadResponse.ok) {
273
- const errorText = await loadResponse.text();
274
- throw new Error(`loadCodeAssist failed: ${loadResponse.status} ${loadResponse.statusText}: ${errorText}`);
289
+ if (!configuredProjectId) {
290
+ if (existingProject) {
291
+ return existingProject;
292
+ }
293
+
294
+ const tierId = getDefaultTierId(loadPayload.allowedTiers);
295
+ onProgress?.("Provisioning project...");
296
+ const onboardBody = {
297
+ tierId,
298
+ ...projectRequest,
299
+ };
300
+ return await onboardProjectWithRetries(endpoint, headers, onboardBody, onProgress);
275
301
  }
276
302
 
277
- const loadPayload = (await loadResponse.json()) as LoadCodeAssistPayload;
278
- const existingProject = readProjectId(loadPayload.cloudaicompanionProject);
279
- if (existingProject) {
280
- return configuredProjectId ?? existingProject;
303
+ if (existingProject === configuredProjectId && loadPayload.currentTier?.id === TIER_STANDARD) {
304
+ return configuredProjectId;
305
+ }
306
+
307
+ const standardTierAvailable = loadPayload.allowedTiers?.some(tier => tier.id === TIER_STANDARD) ?? false;
308
+ if (!standardTierAvailable) {
309
+ throw new Error(`standard-tier is not available for configured project ${configuredProjectId}`);
281
310
  }
282
311
 
283
- const tierId = getDefaultTierId(loadPayload.allowedTiers);
284
312
  onProgress?.("Provisioning project...");
285
313
  const onboardBody = {
286
- tierId,
314
+ tierId: TIER_STANDARD,
287
315
  ...projectRequest,
288
316
  };
289
317
  const provisionedProject = await onboardProjectWithRetries(endpoint, headers, onboardBody, onProgress);
290
- return configuredProjectId ?? provisionedProject;
318
+ if (provisionedProject !== configuredProjectId) {
319
+ throw new Error(`onboardUser returned project ${provisionedProject} instead of ${configuredProjectId}`);
320
+ }
321
+ return configuredProjectId;
291
322
  } catch (error) {
292
323
  throw new Error(
293
324
  `Could not discover or provision an Antigravity project. ${error instanceof Error ? error.message : String(error)}`,