@f5-sales-demo/pi-ai 20.5.0 → 20.6.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.
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.5.0",
4
+ "version": "20.6.0",
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.5.0",
49
+ "@f5-sales-demo/pi-utils": "20.6.0",
50
50
  "@google/genai": "^2.15",
51
51
  "@sinclair/typebox": "^0.34",
52
52
  "@smithy/node-http-handler": "^4.4",
@@ -60,7 +60,14 @@ import { loginQwenPortal } from "./utils/oauth/qwen-portal";
60
60
  import { loginSynthetic } from "./utils/oauth/synthetic";
61
61
  import { loginTavily } from "./utils/oauth/tavily";
62
62
  import { loginTogether } from "./utils/oauth/together";
63
- import type { OAuthController, OAuthCredentials, OAuthProvider, OAuthProviderId } from "./utils/oauth/types";
63
+ import type {
64
+ OAuthController,
65
+ OAuthCredentials,
66
+ OAuthPrompt,
67
+ OAuthProvider,
68
+ OAuthProviderId,
69
+ } from "./utils/oauth/types";
70
+ import { canonicalizeOAuthProviderId } from "./utils/oauth/types";
64
71
  import { loginVenice } from "./utils/oauth/venice";
65
72
  import { loginVercelAiGateway } from "./utils/oauth/vercel-ai-gateway";
66
73
  import { loginVllm } from "./utils/oauth/vllm";
@@ -337,14 +344,14 @@ export class AuthStorage {
337
344
  * Used for CLI --api-key flag.
338
345
  */
339
346
  setRuntimeApiKey(provider: string, apiKey: string): void {
340
- this.#runtimeOverrides.set(provider, apiKey);
347
+ this.#runtimeOverrides.set(canonicalizeOAuthProviderId(provider), apiKey);
341
348
  }
342
349
 
343
350
  /**
344
351
  * Remove a runtime API key override.
345
352
  */
346
353
  removeRuntimeApiKey(provider: string): void {
347
- this.#runtimeOverrides.delete(provider);
354
+ this.#runtimeOverrides.delete(canonicalizeOAuthProviderId(provider));
348
355
  }
349
356
 
350
357
  /**
@@ -640,13 +647,14 @@ export class AuthStorage {
640
647
  * Get credential for a provider (first entry if multiple).
641
648
  */
642
649
  get(provider: string): AuthCredential | undefined {
643
- return this.#getCredentialsForProvider(provider)[0];
650
+ return this.#getCredentialsForProvider(canonicalizeOAuthProviderId(provider))[0];
644
651
  }
645
652
 
646
653
  /**
647
654
  * Set credential for a provider.
648
655
  */
649
656
  async set(provider: string, credential: AuthCredentialEntry): Promise<void> {
657
+ provider = canonicalizeOAuthProviderId(provider);
650
658
  const normalized = Array.isArray(credential) ? credential : [credential];
651
659
  const deduped = this.#dedupeOAuthCredentials(provider, normalized);
652
660
  const stored = this.#store.replaceAuthCredentialsForProvider(provider, deduped);
@@ -670,6 +678,7 @@ export class AuthStorage {
670
678
  * Remove credential for a provider.
671
679
  */
672
680
  async remove(provider: string): Promise<void> {
681
+ provider = canonicalizeOAuthProviderId(provider);
673
682
  this.#store.deleteAuthCredentialsForProvider(provider, "deleted by user");
674
683
  this.#data.delete(provider);
675
684
  this.#resetProviderAssignments(provider);
@@ -686,7 +695,7 @@ export class AuthStorage {
686
695
  * Check if credentials exist for a provider in storage.
687
696
  */
688
697
  has(provider: string): boolean {
689
- return this.#getCredentialsForProvider(provider).length > 0;
698
+ return this.#getCredentialsForProvider(canonicalizeOAuthProviderId(provider)).length > 0;
690
699
  }
691
700
 
692
701
  /**
@@ -694,6 +703,7 @@ export class AuthStorage {
694
703
  * Unlike getApiKey(), this doesn't refresh OAuth tokens.
695
704
  */
696
705
  hasAuth(provider: string): boolean {
706
+ provider = canonicalizeOAuthProviderId(provider);
697
707
  if (this.#runtimeOverrides.has(provider)) return true;
698
708
  if (this.#getCredentialsForProvider(provider).length > 0) return true;
699
709
  if (getEnvApiKey(provider)) return true;
@@ -705,14 +715,16 @@ export class AuthStorage {
705
715
  * Check if OAuth credentials are configured for a provider.
706
716
  */
707
717
  hasOAuth(provider: string): boolean {
708
- return this.#getCredentialsForProvider(provider).some(credential => credential.type === "oauth");
718
+ return this.#getCredentialsForProvider(canonicalizeOAuthProviderId(provider)).some(
719
+ credential => credential.type === "oauth",
720
+ );
709
721
  }
710
722
 
711
723
  /**
712
724
  * Get OAuth credentials for a provider.
713
725
  */
714
726
  getOAuthCredential(provider: string): OAuthCredential | undefined {
715
- return this.#getCredentialsForProvider(provider).find(
727
+ return this.#getCredentialsForProvider(canonicalizeOAuthProviderId(provider)).find(
716
728
  (credential): credential is OAuthCredential => credential.type === "oauth",
717
729
  );
718
730
  }
@@ -742,13 +754,14 @@ export class AuthStorage {
742
754
  /** onAuth is required by auth-storage but optional in OAuthController */
743
755
  onAuth: (info: { url: string; instructions?: string }) => void;
744
756
  /** onPrompt is required for some providers (github-copilot, openai-codex) */
745
- onPrompt: (prompt: { message: string; placeholder?: string }) => Promise<string>;
757
+ onPrompt: (prompt: OAuthPrompt) => Promise<string>;
746
758
  },
747
759
  ): Promise<void> {
748
760
  let credentials: OAuthCredentials;
761
+ const credentialProvider = canonicalizeOAuthProviderId(provider);
749
762
  const saveApiKeyCredential = async (apiKey: string): Promise<void> => {
750
763
  const newCredential: ApiKeyCredential = { type: "api_key", key: apiKey };
751
- await this.set(provider, newCredential);
764
+ await this.set(credentialProvider, newCredential);
752
765
  };
753
766
  const manualCodeInput = () => ctrl.onPrompt({ message: "Paste the authorization code (or full redirect URL):" });
754
767
  switch (provider) {
@@ -783,6 +796,22 @@ export class AuthStorage {
783
796
  onManualCodeInput: ctrl.onManualCodeInput ?? manualCodeInput,
784
797
  });
785
798
  break;
799
+ case "google-antigravity-enterprise":
800
+ credentials = await loginAntigravity(
801
+ {
802
+ ...ctrl,
803
+ onManualCodeInput: ctrl.onManualCodeInput ?? manualCodeInput,
804
+ },
805
+ {
806
+ enterpriseRequired: true,
807
+ projectSources: {
808
+ environment: {},
809
+ readAntigravityProjectId: async () => undefined,
810
+ readGcloudProjectId: async () => undefined,
811
+ },
812
+ },
813
+ );
814
+ break;
786
815
  case "openai-codex":
787
816
  credentials = await loginOpenAICodex({
788
817
  ...ctrl,
@@ -960,7 +989,7 @@ export class AuthStorage {
960
989
  }
961
990
  }
962
991
  const newCredential: OAuthCredential = { type: "oauth", ...credentials };
963
- await this.#upsertOAuthCredential(provider, newCredential);
992
+ await this.#upsertOAuthCredential(credentialProvider, newCredential);
964
993
  }
965
994
 
966
995
  /**
@@ -983,6 +1012,7 @@ export class AuthStorage {
983
1012
  expiresAt: credential.expires,
984
1013
  accountId: credential.accountId,
985
1014
  projectId: credential.projectId,
1015
+ tierId: credential.tierId,
986
1016
  email: credential.email,
987
1017
  enterpriseUrl: credential.enterpriseUrl,
988
1018
  };
@@ -1057,6 +1087,7 @@ export class AuthStorage {
1057
1087
  expires: credential.expiresAt,
1058
1088
  accountId: credential.accountId,
1059
1089
  projectId: credential.projectId,
1090
+ tierId: credential.tierId,
1060
1091
  email: credential.email,
1061
1092
  enterpriseUrl: credential.enterpriseUrl,
1062
1093
  };
@@ -1070,6 +1101,7 @@ export class AuthStorage {
1070
1101
  expiresAt: refreshed.expires,
1071
1102
  accountId: refreshed.accountId ?? credential.accountId,
1072
1103
  projectId: refreshed.projectId ?? credential.projectId,
1104
+ tierId: refreshed.tierId ?? credential.tierId,
1073
1105
  email: refreshed.email ?? credential.email,
1074
1106
  enterpriseUrl: refreshed.enterpriseUrl ?? credential.enterpriseUrl,
1075
1107
  };
@@ -1097,6 +1129,7 @@ export class AuthStorage {
1097
1129
  expires: next.expiresAt ?? existing.expires,
1098
1130
  accountId: next.accountId,
1099
1131
  projectId: next.projectId,
1132
+ tierId: next.tierId ?? existing.tierId,
1100
1133
  email: next.email,
1101
1134
  enterpriseUrl: next.enterpriseUrl,
1102
1135
  });
@@ -1826,6 +1859,7 @@ export class AuthStorage {
1826
1859
  accountId: result.newCredentials.accountId ?? selection.credential.accountId,
1827
1860
  email: result.newCredentials.email ?? selection.credential.email,
1828
1861
  projectId: result.newCredentials.projectId ?? selection.credential.projectId,
1862
+ tierId: result.newCredentials.tierId ?? selection.credential.tierId,
1829
1863
  enterpriseUrl: result.newCredentials.enterpriseUrl ?? selection.credential.enterpriseUrl,
1830
1864
  };
1831
1865
  this.#replaceCredentialAt(provider, selection.index, updated);
@@ -1886,6 +1920,7 @@ export class AuthStorage {
1886
1920
  * routing metadata so discovery can hit the correct host.
1887
1921
  */
1888
1922
  async peekApiKey(provider: string): Promise<string | undefined> {
1923
+ provider = canonicalizeOAuthProviderId(provider);
1889
1924
  const runtimeKey = this.#runtimeOverrides.get(provider);
1890
1925
  if (runtimeKey) {
1891
1926
  return runtimeKey;
@@ -1927,6 +1962,7 @@ export class AuthStorage {
1927
1962
  * 5. Fallback resolver (models.json custom providers)
1928
1963
  */
1929
1964
  async getApiKey(provider: string, sessionId?: string, options?: AuthApiKeyOptions): Promise<string | undefined> {
1965
+ provider = canonicalizeOAuthProviderId(provider);
1930
1966
  // Runtime override takes highest priority
1931
1967
  const runtimeKey = this.#runtimeOverrides.get(provider);
1932
1968
  if (runtimeKey) {
package/src/usage.ts CHANGED
@@ -87,6 +87,7 @@ export interface UsageCredential {
87
87
  expiresAt?: number;
88
88
  accountId?: string;
89
89
  projectId?: string;
90
+ tierId?: string;
90
91
  email?: string;
91
92
  enterpriseUrl?: string;
92
93
  metadata?: Record<string, unknown>;
@@ -81,6 +81,11 @@ export interface AntigravityProjectSources {
81
81
 
82
82
  export interface AntigravityLoginOptions {
83
83
  projectSources?: AntigravityProjectSources;
84
+ enterpriseRequired?: boolean;
85
+ }
86
+
87
+ export interface AntigravityProjectPolicy {
88
+ enterpriseRequired?: boolean;
84
89
  }
85
90
 
86
91
  function normalizeProjectId(value: unknown): string | undefined {
@@ -134,6 +139,7 @@ async function readExternalProjectId(source: () => Promise<string | undefined>):
134
139
  export async function resolveAntigravityProjectId(
135
140
  ctrl: OAuthController,
136
141
  sources: AntigravityProjectSources = {},
142
+ policy: AntigravityProjectPolicy = {},
137
143
  ): Promise<string | undefined> {
138
144
  const environment = sources.environment ?? {
139
145
  GOOGLE_CLOUD_PROJECT: $env.GOOGLE_CLOUD_PROJECT,
@@ -160,13 +166,22 @@ export async function resolveAntigravityProjectId(
160
166
  return gcloudProjectId;
161
167
  }
162
168
 
163
- if (!ctrl.onPrompt) return undefined;
169
+ const enterpriseError = "Enterprise authentication requires an explicit Google Cloud project";
170
+ if (!ctrl.onPrompt) {
171
+ if (policy.enterpriseRequired) throw new Error(enterpriseError);
172
+ return undefined;
173
+ }
164
174
  const input = await ctrl.onPrompt({
165
- message: "Google Cloud project ID (leave blank to use individual-tier discovery):",
175
+ message: policy.enterpriseRequired
176
+ ? "Enterprise Google Cloud project ID (required; individual/free tier is disabled):"
177
+ : "Google Cloud project ID (leave blank to use individual-tier discovery):",
166
178
  placeholder: "my-enterprise-project",
167
- allowEmpty: true,
179
+ allowEmpty: !policy.enterpriseRequired,
168
180
  });
169
- if (!input.trim()) return undefined;
181
+ if (!input.trim()) {
182
+ if (policy.enterpriseRequired) throw new Error(enterpriseError);
183
+ return undefined;
184
+ }
170
185
  const promptedProjectId = normalizeProjectId(input);
171
186
  if (!promptedProjectId) {
172
187
  throw new Error(
@@ -404,6 +419,7 @@ class AntigravityOAuthFlow extends OAuthCallbackFlow {
404
419
  access: tokenData.access_token,
405
420
  expires: Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000,
406
421
  projectId,
422
+ ...(this.#configuredProjectId ? { tierId: TIER_STANDARD } : {}),
407
423
  email,
408
424
  };
409
425
  }
@@ -416,7 +432,9 @@ export async function loginAntigravity(
416
432
  ctrl: OAuthController,
417
433
  options: AntigravityLoginOptions = {},
418
434
  ): Promise<OAuthCredentials> {
419
- const configuredProjectId = await resolveAntigravityProjectId(ctrl, options.projectSources);
435
+ const configuredProjectId = await resolveAntigravityProjectId(ctrl, options.projectSources, {
436
+ enterpriseRequired: options.enterpriseRequired,
437
+ });
420
438
  const flow = new AntigravityOAuthFlow(ctrl, configuredProjectId);
421
439
  return flow.login();
422
440
  }
@@ -186,6 +186,13 @@ const builtInOAuthProviders: OAuthProviderInfo[] = [
186
186
  name: "Antigravity (Gemini 3, Claude, GPT-OSS)",
187
187
  available: true,
188
188
  },
189
+ {
190
+ id: "google-antigravity-enterprise",
191
+ name: "Google Antigravity Enterprise (Gemini 3.6 Flash High)",
192
+ available: true,
193
+ canonicalId: "google-antigravity",
194
+ loginOnly: true,
195
+ },
189
196
  {
190
197
  id: "cursor",
191
198
  name: "Cursor (Claude, GPT, etc.)",
@@ -68,6 +68,7 @@ export async function loginLiteLLM(options: LiteLLMLoginOptions): Promise<LiteLL
68
68
  const rawKey = await options.onPrompt({
69
69
  message: `LiteLLM API Key${defaultKeyHint}`,
70
70
  placeholder: "sk-...",
71
+ secret: true,
71
72
  });
72
73
 
73
74
  if (options.signal?.aborted) throw new Error("Login cancelled");
@@ -4,6 +4,8 @@ export type OAuthCredentials = {
4
4
  expires: number;
5
5
  enterpriseUrl?: string;
6
6
  projectId?: string;
7
+ /** Durable entitlement certified during enterprise project discovery. */
8
+ tierId?: string;
7
9
  email?: string;
8
10
  accountId?: string;
9
11
  };
@@ -17,6 +19,7 @@ export type OAuthProvider =
17
19
  | "github-copilot"
18
20
  | "google-gemini-cli"
19
21
  | "google-antigravity"
22
+ | "google-antigravity-enterprise"
20
23
  | "gitlab-duo"
21
24
  | "huggingface"
22
25
  | "kimi-code"
@@ -53,6 +56,8 @@ export type OAuthPrompt = {
53
56
  message: string;
54
57
  placeholder?: string;
55
58
  allowEmpty?: boolean;
59
+ /** Render this prompt with obscured input because it collects a credential. */
60
+ secret?: boolean;
56
61
  };
57
62
 
58
63
  export type OAuthAuthInfo = {
@@ -64,6 +69,15 @@ export interface OAuthProviderInfo {
64
69
  id: OAuthProviderId;
65
70
  name: string;
66
71
  available: boolean;
72
+ /** Provider ID used for persisted credentials and model routing. */
73
+ canonicalId?: OAuthProvider;
74
+ /** Entry is offered for login but omitted from logout to avoid alias duplicates. */
75
+ loginOnly?: boolean;
76
+ }
77
+
78
+ /** Resolve login-only aliases to the provider ID used by credentials and models. */
79
+ export function canonicalizeOAuthProviderId(provider: string): string {
80
+ return provider === "google-antigravity-enterprise" ? "google-antigravity" : provider;
67
81
  }
68
82
 
69
83
  export interface OAuthController {