@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.
|
|
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.
|
|
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",
|
package/src/auth-storage.ts
CHANGED
|
@@ -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 {
|
|
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(
|
|
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:
|
|
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(
|
|
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(
|
|
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
|
@@ -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
|
-
|
|
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:
|
|
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:
|
|
179
|
+
allowEmpty: !policy.enterpriseRequired,
|
|
168
180
|
});
|
|
169
|
-
if (!input.trim())
|
|
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
|
}
|
package/src/utils/oauth/index.ts
CHANGED
|
@@ -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");
|
package/src/utils/oauth/types.ts
CHANGED
|
@@ -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 {
|