@f5-sales-demo/pi-ai 20.4.1 → 20.4.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.4.1",
4
+ "version": "20.4.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.4.1",
49
+ "@f5-sales-demo/pi-utils": "20.4.3",
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";
@@ -62,6 +62,9 @@ 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
64
  const ANTIGRAVITY_ENDPOINT_FALLBACKS = [ANTIGRAVITY_DAILY_ENDPOINT, ANTIGRAVITY_SANDBOX_ENDPOINT] as const;
65
+ const ANTIGRAVITY_VERTEX_MODELS: Readonly<Record<string, string>> = Object.freeze({
66
+ "gemini-3.1-pro-high-vertex": "gemini-3.1-pro-preview",
67
+ });
65
68
 
66
69
  /**
67
70
  * Build a User-Agent string that identifies as Gemini CLI to unlock higher rate limits.
@@ -421,34 +424,36 @@ interface CloudCodeAssistRequest {
421
424
  requestId?: string;
422
425
  }
423
426
 
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;
427
+ interface GeminiGenerateContentResponse {
428
+ candidates?: Array<{
429
+ content?: {
430
+ role: string;
431
+ parts?: Array<{
432
+ text?: string;
433
+ thought?: boolean;
434
+ thoughtSignature?: string;
435
+ functionCall?: {
436
+ name: string;
437
+ args: Record<string, unknown>;
438
+ id?: string;
439
+ };
440
+ }>;
448
441
  };
449
- modelVersion?: string;
450
- responseId?: string;
442
+ finishReason?: string;
443
+ }>;
444
+ usageMetadata?: {
445
+ promptTokenCount?: number;
446
+ candidatesTokenCount?: number;
447
+ thoughtsTokenCount?: number;
448
+ totalTokenCount?: number;
449
+ cachedContentTokenCount?: number;
451
450
  };
451
+ modelVersion?: string;
452
+ responseId?: string;
453
+ }
454
+
455
+ interface CloudCodeAssistResponseChunk extends GeminiGenerateContentResponse {
456
+ response?: GeminiGenerateContentResponse;
452
457
  traceId?: string;
453
458
  }
454
459
 
@@ -492,11 +497,16 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
492
497
  const parsedCredentials = parseGeminiCliCredentials(apiKeyRaw);
493
498
  const activeCredentials = await refreshGeminiCliCredentialsIfNeeded(parsedCredentials, isAntigravity);
494
499
  const { accessToken, projectId } = activeCredentials;
500
+ const vertexModelId = isAntigravity ? ANTIGRAVITY_VERTEX_MODELS[model.id] : undefined;
501
+ const serviceName = vertexModelId ? "Vertex AI" : "Cloud Code Assist";
495
502
 
496
503
  const baseUrl = model.baseUrl?.trim();
497
504
  const endpoints = baseUrl ? [baseUrl] : isAntigravity ? ANTIGRAVITY_ENDPOINT_FALLBACKS : [DEFAULT_ENDPOINT];
498
505
 
499
- let requestBody = buildRequest(model, context, projectId, options, isAntigravity);
506
+ const cloudCodeRequest = buildRequest(model, context, projectId, options, isAntigravity);
507
+ let requestBody: CloudCodeAssistRequest | CloudCodeAssistRequest["request"] = vertexModelId
508
+ ? cloudCodeRequest.request
509
+ : cloudCodeRequest;
500
510
  const replacementPayload = await options?.onPayload?.(requestBody, model);
501
511
  if (replacementPayload !== undefined) {
502
512
  requestBody = replacementPayload as typeof requestBody;
@@ -534,7 +544,9 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
534
544
 
535
545
  try {
536
546
  const endpoint = endpoints[Math.min(attempt, endpoints.length - 1)];
537
- requestUrl = `${endpoint}/v1internal:streamGenerateContent?alt=sse`;
547
+ requestUrl = vertexModelId
548
+ ? `${endpoint}/v1/projects/${encodeURIComponent(projectId)}/locations/global/publishers/google/models/${encodeURIComponent(vertexModelId)}:streamGenerateContent?alt=sse`
549
+ : `${endpoint}/v1internal:streamGenerateContent?alt=sse`;
538
550
  response = await fetch(requestUrl, {
539
551
  method: "POST",
540
552
  headers: requestHeaders,
@@ -552,7 +564,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
552
564
  if (response.status === 429) {
553
565
  if (/quota|exhausted/i.test(errorText)) {
554
566
  throw withHttpStatus(
555
- new Error(`Cloud Code Assist API error (429): ${extractErrorMessage(errorText)}`),
567
+ new Error(`${serviceName} API error (429): ${extractErrorMessage(errorText)}`),
556
568
  429,
557
569
  );
558
570
  }
@@ -590,7 +602,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
590
602
 
591
603
  // Not retryable or budget exceeded
592
604
  throw withHttpStatus(
593
- new Error(`Cloud Code Assist API error (${response.status}): ${extractErrorMessage(errorText)}`),
605
+ new Error(`${serviceName} API error (${response.status}): ${extractErrorMessage(errorText)}`),
594
606
  response.status,
595
607
  );
596
608
  } catch (error) {
@@ -665,8 +677,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
665
677
  activeResponse.body!,
666
678
  options?.signal,
667
679
  )) {
668
- const responseData = chunk.response;
669
- if (!responseData) continue;
680
+ const responseData = chunk.response ?? chunk;
670
681
 
671
682
  const candidate = responseData.candidates?.[0];
672
683
  if (candidate?.content?.parts) {
@@ -878,7 +889,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
878
889
  if (!currentResponse.ok) {
879
890
  const retryErrorText = await currentResponse.text();
880
891
  throw withHttpStatus(
881
- new Error(`Cloud Code Assist API error (${currentResponse.status}): ${retryErrorText}`),
892
+ new Error(`${serviceName} API error (${currentResponse.status}): ${retryErrorText}`),
882
893
  currentResponse.status,
883
894
  );
884
895
  }
@@ -896,7 +907,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
896
907
  }
897
908
 
898
909
  if (!receivedContent) {
899
- throw new Error("Cloud Code Assist API returned an empty response");
910
+ throw new Error(`${serviceName} API returned an empty response`);
900
911
  }
901
912
 
902
913
  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)}`,