@f5-sales-demo/pi-ai 21.2.0 → 21.4.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/README.md +1 -1
- package/package.json +2 -2
- package/src/auth-storage.ts +0 -7
- package/src/cli.ts +0 -16
- package/src/providers/google-vertex.ts +10 -25
- package/src/stream.ts +2 -9
- package/src/utils/oauth/index.ts +7 -11
- package/src/utils/oauth/openai-codex.ts +43 -6
- package/src/utils/oauth/types.ts +4 -3
package/README.md
CHANGED
|
@@ -1047,7 +1047,7 @@ bunx @f5-sales-demo/pi-ai list # list available providers
|
|
|
1047
1047
|
Credentials are saved to `agent.db` in the agent directory. `/login qianfan` opens the Qianfan console and stores the pasted API key.
|
|
1048
1048
|
|
|
1049
1049
|
`login` supports OAuth providers (Anthropic, GitHub Copilot, Gemini CLI, Antigravity) and API-key onboarding flows.
|
|
1050
|
-
OpenAI has two credential paths. Use `OPENAI_API_KEY` for usage-based Platform API access. Use `login openai-codex` for ChatGPT Plus/Pro subscription OAuth; SSH/headless terminals automatically receive a short device code, while local desktops use browser PKCE.
|
|
1050
|
+
OpenAI has two credential paths. Use `OPENAI_API_KEY` for usage-based Platform API access. Use `login openai-codex` for ChatGPT Plus/Pro subscription OAuth; SSH/headless terminals automatically receive a short device code, while local desktops use browser PKCE. `login openai` explains the API-key path.
|
|
1051
1051
|
|
|
1052
1052
|
For the current OpenAI-compatible integrations, API-key onboarding covers Together, Moonshot, Qianfan, NVIDIA, NanoGPT, Hugging Face, Venice, Xiaomi, vLLM, LiteLLM, Cloudflare AI Gateway, and Qwen Portal. Ollama is typically local and unauthenticated; set `OLLAMA_API_KEY` only when your Ollama deployment enforces bearer auth.
|
|
1053
1053
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5-sales-demo/pi-ai",
|
|
4
|
-
"version": "21.
|
|
4
|
+
"version": "21.4.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": "21.
|
|
49
|
+
"@f5-sales-demo/pi-utils": "21.4.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
|
@@ -821,13 +821,6 @@ export class AuthStorage {
|
|
|
821
821
|
onManualCodeInput: ctrl.onManualCodeInput ?? manualCodeInput,
|
|
822
822
|
});
|
|
823
823
|
break;
|
|
824
|
-
case "openai-codex-browser":
|
|
825
|
-
credentials = await loginOpenAICodex({
|
|
826
|
-
...ctrl,
|
|
827
|
-
method: "browser",
|
|
828
|
-
onManualCodeInput: ctrl.onManualCodeInput ?? manualCodeInput,
|
|
829
|
-
});
|
|
830
|
-
break;
|
|
831
824
|
case "openai":
|
|
832
825
|
throw new Error("OpenAI uses usage-based API access. Set OPENAI_API_KEY instead of signing in.");
|
|
833
826
|
case "gitlab-duo":
|
package/src/cli.ts
CHANGED
|
@@ -140,21 +140,6 @@ async function login(provider: OAuthProvider): Promise<void> {
|
|
|
140
140
|
},
|
|
141
141
|
});
|
|
142
142
|
break;
|
|
143
|
-
case "openai-codex-browser":
|
|
144
|
-
credentials = await loginOpenAICodex({
|
|
145
|
-
method: "browser",
|
|
146
|
-
onAuth(info) {
|
|
147
|
-
const { url, instructions } = info;
|
|
148
|
-
console.log(`\nOpen this URL in your browser:\n${url}`);
|
|
149
|
-
if (instructions) console.log(instructions);
|
|
150
|
-
console.log();
|
|
151
|
-
},
|
|
152
|
-
async onPrompt(p) {
|
|
153
|
-
return await promptFn(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`);
|
|
154
|
-
},
|
|
155
|
-
});
|
|
156
|
-
break;
|
|
157
|
-
|
|
158
143
|
case "kimi-code":
|
|
159
144
|
credentials = await loginKimi({
|
|
160
145
|
onAuth(info) {
|
|
@@ -351,7 +336,6 @@ Providers:
|
|
|
351
336
|
google-gemini-cli Google Gemini CLI
|
|
352
337
|
google-antigravity Antigravity (Gemini 3, Claude, GPT-OSS)
|
|
353
338
|
openai-codex ChatGPT Plus/Pro (Codex subscription)
|
|
354
|
-
openai-codex-browser ChatGPT Plus/Pro (explicit browser callback)
|
|
355
339
|
openai OpenAI Responses API (usage-based; set OPENAI_API_KEY)
|
|
356
340
|
kimi-code Kimi Code
|
|
357
341
|
kilo Kilo Gateway
|
|
@@ -104,10 +104,11 @@ export const streamGoogleVertex: StreamFunction<"google-vertex"> = (
|
|
|
104
104
|
let rawRequestDump: RawHttpRequestDump | undefined;
|
|
105
105
|
|
|
106
106
|
try {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
const
|
|
107
|
+
// Corporate Vertex is deliberately ADC-only. In particular, do not let the
|
|
108
|
+
// generic agent API-key plumbing turn this route into Gemini consumer auth.
|
|
109
|
+
const project = await resolveGoogleVertexProject(options);
|
|
110
|
+
const location = resolveGoogleVertexLocation(options);
|
|
111
|
+
const client = createClient(model, project, location);
|
|
111
112
|
const params = buildGoogleVertexParams(model, context, options);
|
|
112
113
|
options?.onPayload?.(params);
|
|
113
114
|
rawRequestDump = {
|
|
@@ -115,9 +116,7 @@ export const streamGoogleVertex: StreamFunction<"google-vertex"> = (
|
|
|
115
116
|
api: output.api,
|
|
116
117
|
model: model.id,
|
|
117
118
|
method: "POST",
|
|
118
|
-
url:
|
|
119
|
-
? `https://aiplatform.googleapis.com/${API_VERSION}/publishers/google/models/${model.id}:streamGenerateContent`
|
|
120
|
-
: googleVertexRequestUrl(model.id, project!, location!),
|
|
119
|
+
url: googleVertexRequestUrl(model.id, project, location),
|
|
121
120
|
body: params,
|
|
122
121
|
};
|
|
123
122
|
const googleStream = await client.models.generateContentStream(params);
|
|
@@ -342,23 +341,6 @@ function createClient(model: Model<"google-vertex">, project: string, location:
|
|
|
342
341
|
});
|
|
343
342
|
}
|
|
344
343
|
|
|
345
|
-
function createClientWithApiKey(model: Model<"google-vertex">, apiKey: string): GoogleGenAI {
|
|
346
|
-
return new GoogleGenAI({
|
|
347
|
-
vertexai: true,
|
|
348
|
-
apiKey,
|
|
349
|
-
apiVersion: API_VERSION,
|
|
350
|
-
httpOptions: buildHttpOptions(model),
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function resolveApiKey(options?: GoogleVertexOptions): string | undefined {
|
|
355
|
-
// options.apiKey may contain sentinel values like "<authenticated>" or "N/A"
|
|
356
|
-
// leaked from the agent loop — only use it if it looks like a real API key.
|
|
357
|
-
const optKey = options?.apiKey;
|
|
358
|
-
const realKey = optKey && !optKey.startsWith("<") && optKey !== "N/A" ? optKey : undefined;
|
|
359
|
-
return realKey || $env.GOOGLE_CLOUD_API_KEY;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
344
|
const defaultProjectRuntime: GoogleVertexProjectRuntime = {
|
|
363
345
|
readAdcProject,
|
|
364
346
|
readLocalConfigProject: readConfiguredGcloudProject,
|
|
@@ -442,7 +424,10 @@ export async function resolveGoogleVertexProject(
|
|
|
442
424
|
}
|
|
443
425
|
|
|
444
426
|
export function resolveGoogleVertexLocation(options?: GoogleVertexOptions): string {
|
|
445
|
-
|
|
427
|
+
// Gemini 3.7 Flash Corporate is served through the global endpoint only.
|
|
428
|
+
// Ignore inherited provider/environment locations so a request cannot drift.
|
|
429
|
+
void options;
|
|
430
|
+
return "global";
|
|
446
431
|
}
|
|
447
432
|
|
|
448
433
|
async function readAdcProject(): Promise<string | undefined> {
|
package/src/stream.ts
CHANGED
|
@@ -97,15 +97,8 @@ const serviceProviderMap: Record<string, KeyResolver> = {
|
|
|
97
97
|
? $pickenv("ANTHROPIC_FOUNDRY_API_KEY", "ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY")
|
|
98
98
|
: $pickenv("ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"),
|
|
99
99
|
"gitlab-duo": "GITLAB_TOKEN",
|
|
100
|
-
// Vertex
|
|
101
|
-
"google-vertex": () =>
|
|
102
|
-
if ($env.GOOGLE_CLOUD_API_KEY) {
|
|
103
|
-
return $env.GOOGLE_CLOUD_API_KEY;
|
|
104
|
-
}
|
|
105
|
-
if (hasVertexAdcCredentials()) {
|
|
106
|
-
return "<authenticated>";
|
|
107
|
-
}
|
|
108
|
-
},
|
|
100
|
+
// Corporate Vertex uses Application Default Credentials only.
|
|
101
|
+
"google-vertex": () => (hasVertexAdcCredentials() ? "<authenticated>" : undefined),
|
|
109
102
|
// Amazon Bedrock supports multiple credential sources:
|
|
110
103
|
// 1. AWS_PROFILE - named profile from ~/.aws/credentials
|
|
111
104
|
// 2. AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY - standard IAM keys
|
package/src/utils/oauth/index.ts
CHANGED
|
@@ -156,13 +156,6 @@ const builtInOAuthProviders: OAuthProviderInfo[] = [
|
|
|
156
156
|
name: "ChatGPT Plus/Pro (Codex Subscription)",
|
|
157
157
|
available: true,
|
|
158
158
|
},
|
|
159
|
-
{
|
|
160
|
-
id: "openai-codex-browser",
|
|
161
|
-
name: "ChatGPT Plus/Pro (Browser callback)",
|
|
162
|
-
available: true,
|
|
163
|
-
canonicalId: "openai-codex",
|
|
164
|
-
loginOnly: true,
|
|
165
|
-
},
|
|
166
159
|
{
|
|
167
160
|
id: "openai",
|
|
168
161
|
name: "OpenAI Responses API (usage-based API access)",
|
|
@@ -206,14 +199,14 @@ const builtInOAuthProviders: OAuthProviderInfo[] = [
|
|
|
206
199
|
{
|
|
207
200
|
id: "google-antigravity",
|
|
208
201
|
name: "Antigravity (Gemini 3, Claude, GPT-OSS)",
|
|
202
|
+
description: "Optional consumer or non-corporate route",
|
|
209
203
|
available: true,
|
|
210
204
|
},
|
|
211
205
|
{
|
|
212
206
|
id: "google-antigravity-enterprise",
|
|
213
|
-
name: "Google Antigravity Enterprise (
|
|
207
|
+
name: "Google Antigravity Enterprise (Advanced OAuth)",
|
|
208
|
+
description: "Advanced corporate OAuth route; entitlement-discovered models, not Vertex ADC",
|
|
214
209
|
available: true,
|
|
215
|
-
canonicalId: "google-antigravity",
|
|
216
|
-
loginOnly: true,
|
|
217
210
|
},
|
|
218
211
|
{
|
|
219
212
|
id: "cursor",
|
|
@@ -516,7 +509,10 @@ export async function getOAuthApiKey(
|
|
|
516
509
|
}
|
|
517
510
|
// For providers that need request-time credential metadata, return JSON.
|
|
518
511
|
const needsStructuredApiKey =
|
|
519
|
-
provider === "github-copilot" ||
|
|
512
|
+
provider === "github-copilot" ||
|
|
513
|
+
provider === "google-gemini-cli" ||
|
|
514
|
+
provider === "google-antigravity" ||
|
|
515
|
+
provider === "google-antigravity-enterprise";
|
|
520
516
|
const apiKey = needsStructuredApiKey
|
|
521
517
|
? JSON.stringify({
|
|
522
518
|
token: creds.access,
|
|
@@ -254,16 +254,55 @@ export type OpenAICodexLoginOptions = OAuthController & {
|
|
|
254
254
|
method?: OpenAICodexLoginMethod;
|
|
255
255
|
};
|
|
256
256
|
|
|
257
|
-
export
|
|
258
|
-
|
|
259
|
-
|
|
257
|
+
export type OpenAICodexLoginDependencies = {
|
|
258
|
+
deviceLogin?: (options: OAuthController) => Promise<OAuthCredentials>;
|
|
259
|
+
browserLogin?: (options: OAuthController & { originator?: string }) => Promise<OAuthCredentials>;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
export class OpenAICodexDeviceUnavailableError extends Error {
|
|
263
|
+
constructor() {
|
|
264
|
+
super(
|
|
265
|
+
"Device-code login is not enabled for this ChatGPT account or workspace. Enable it in ChatGPT security or workspace settings, or continue with browser/manual redirect login.",
|
|
266
|
+
);
|
|
267
|
+
this.name = "OpenAICodexDeviceUnavailableError";
|
|
260
268
|
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function isDeviceAuthorizationUnavailable(error: unknown): boolean {
|
|
272
|
+
return error instanceof OpenAICodexDeviceUnavailableError;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function loginOpenAICodexBrowser(options: OAuthController & { originator?: string }): Promise<OAuthCredentials> {
|
|
261
276
|
const pkce = await generatePKCE();
|
|
262
277
|
const originator = options.originator?.trim() || "pi";
|
|
263
278
|
const flow = new OpenAICodexOAuthFlow(options, pkce, originator);
|
|
264
279
|
return flow.login();
|
|
265
280
|
}
|
|
266
281
|
|
|
282
|
+
export async function loginOpenAICodex(
|
|
283
|
+
options: OpenAICodexLoginOptions,
|
|
284
|
+
dependencies: OpenAICodexLoginDependencies = {},
|
|
285
|
+
): Promise<OAuthCredentials> {
|
|
286
|
+
const browserLogin = dependencies.browserLogin ?? loginOpenAICodexBrowser;
|
|
287
|
+
if (resolveOpenAICodexLoginMethod(options.method) !== "device") {
|
|
288
|
+
return browserLogin(options);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
return await (dependencies.deviceLogin ?? loginOpenAICodexDevice)(options);
|
|
293
|
+
} catch (error) {
|
|
294
|
+
if (!isDeviceAuthorizationUnavailable(error) || !options.onPrompt) throw error;
|
|
295
|
+
const response = await options.onPrompt({
|
|
296
|
+
message: "ChatGPT device authorization is unavailable. Continue with browser/manual redirect login? [Y/n]",
|
|
297
|
+
placeholder: "Press Enter to continue, or type no to cancel",
|
|
298
|
+
allowEmpty: true,
|
|
299
|
+
});
|
|
300
|
+
if (/^(?:n|no)$/i.test(response.trim())) throw new Error("ChatGPT login cancelled");
|
|
301
|
+
options.onProgress?.("Continuing with browser/manual redirect authentication…");
|
|
302
|
+
return browserLogin(options);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
267
306
|
export type OpenAICodexDeviceFlowDependencies = {
|
|
268
307
|
fetch?: typeof fetch;
|
|
269
308
|
sleep?: (milliseconds: number, signal?: AbortSignal) => Promise<void>;
|
|
@@ -289,9 +328,7 @@ export async function loginOpenAICodexDevice(
|
|
|
289
328
|
});
|
|
290
329
|
if (!initResponse.ok) {
|
|
291
330
|
if (initResponse.status === 404) {
|
|
292
|
-
throw new
|
|
293
|
-
"Device-code login is not enabled for this ChatGPT account or workspace. Enable it in ChatGPT security or workspace settings, or choose the browser callback login.",
|
|
294
|
-
);
|
|
331
|
+
throw new OpenAICodexDeviceUnavailableError();
|
|
295
332
|
}
|
|
296
333
|
const detail = formatOpenAICodexTokenEndpointError(initResponse.status, await initResponse.text());
|
|
297
334
|
throw new Error(`Device authorization initiation failed: ${detail}`);
|
package/src/utils/oauth/types.ts
CHANGED
|
@@ -35,7 +35,6 @@ export type OAuthProvider =
|
|
|
35
35
|
| "ollama"
|
|
36
36
|
| "openai"
|
|
37
37
|
| "openai-codex"
|
|
38
|
-
| "openai-codex-browser"
|
|
39
38
|
| "opencode-go"
|
|
40
39
|
| "opencode-zen"
|
|
41
40
|
| "parallel"
|
|
@@ -75,12 +74,14 @@ export interface OAuthProviderInfo {
|
|
|
75
74
|
canonicalId?: OAuthProvider;
|
|
76
75
|
/** Entry is offered for login but omitted from logout to avoid alias duplicates. */
|
|
77
76
|
loginOnly?: boolean;
|
|
77
|
+
/** Short secondary text displayed by interactive login pickers. */
|
|
78
|
+
description?: string;
|
|
79
|
+
/** Stable login-menu order; lower values are shown first. */
|
|
80
|
+
loginOrder?: number;
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
/** Resolve login-only aliases to the provider ID used by credentials and models. */
|
|
81
84
|
export function canonicalizeOAuthProviderId(provider: string): string {
|
|
82
|
-
if (provider === "google-antigravity-enterprise") return "google-antigravity";
|
|
83
|
-
if (provider === "openai-codex-browser") return "openai-codex";
|
|
84
85
|
return provider;
|
|
85
86
|
}
|
|
86
87
|
|