@f5-sales-demo/pi-ai 20.2.7 → 20.3.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/CHANGELOG.md +20 -1
- package/package.json +5 -3
- package/src/models.json +52 -2
- package/src/provider-models/descriptors.ts +1 -1
- package/src/providers/google-gemini-cli.ts +6 -2
- package/src/providers/google-vertex.ts +71 -17
- package/src/providers/google.ts +5 -2
- package/src/stream.ts +1 -4
- package/src/types.ts +2 -0
- package/src/utils/anthropic-auth.ts +46 -24
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
|
+
<!-- markdownlint-configure-file { "MD024": { "siblings_only": true } } -->
|
|
2
|
+
|
|
1
3
|
# Changelog
|
|
2
4
|
|
|
3
|
-
## [
|
|
5
|
+
## [20.3.0] - 2026-08-04
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Changed the LiteLLM provider fallback default to GPT-5.6 Sol ([#2886](https://github.com/f5-sales-demo/xcsh/issues/2886))
|
|
10
|
+
- Updated the default Antigravity client version to 2.4.3 for current Google Cloud Code Assist compatibility ([#2874](https://github.com/f5-sales-demo/xcsh/issues/2874))
|
|
11
|
+
- Updated the Google GenAI SDK to 2.15.0 ([#2874](https://github.com/f5-sales-demo/xcsh/issues/2874))
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Added Gemini 3.6 Flash through Google Vertex AI with ADC project discovery, gcloud project fallback, and a global location default ([#2874](https://github.com/f5-sales-demo/xcsh/issues/2874))
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Preserved native Gemini finish reasons in Google provider errors instead of reporting an unknown error ([#2874](https://github.com/f5-sales-demo/xcsh/issues/2874))
|
|
20
|
+
|
|
21
|
+
<!-- markdownlint-disable MD024 -->
|
|
22
|
+
<!-- textlint-disable terminology -->
|
|
4
23
|
|
|
5
24
|
## [19.105.1] - 2026-07-31
|
|
6
25
|
|
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.3.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",
|
|
@@ -32,9 +32,11 @@
|
|
|
32
32
|
"pi-ai": "./src/cli.ts"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
|
+
"precheck": "bun ../../scripts/check-installed-dependencies.ts",
|
|
35
36
|
"check": "biome check . && bun run check:types",
|
|
36
37
|
"check:types": "tsgo -p tsconfig.json --noEmit",
|
|
37
38
|
"lint": "biome lint .",
|
|
39
|
+
"pretest": "bun ../../scripts/check-installed-dependencies.ts",
|
|
38
40
|
"test": "bun test",
|
|
39
41
|
"fix": "biome check --write --unsafe .",
|
|
40
42
|
"fmt": "biome format --write .",
|
|
@@ -44,8 +46,8 @@
|
|
|
44
46
|
"@anthropic-ai/sdk": "^0.115",
|
|
45
47
|
"@aws-sdk/client-bedrock-runtime": "^3",
|
|
46
48
|
"@bufbuild/protobuf": "^2.11",
|
|
47
|
-
"@
|
|
48
|
-
"@
|
|
49
|
+
"@f5-sales-demo/pi-utils": "20.3.0",
|
|
50
|
+
"@google/genai": "^2.15",
|
|
49
51
|
"@sinclair/typebox": "^0.34",
|
|
50
52
|
"@smithy/node-http-handler": "^4.4",
|
|
51
53
|
"ajv": "^8.20",
|
package/src/models.json
CHANGED
|
@@ -6707,6 +6707,31 @@
|
|
|
6707
6707
|
"maxLevel": "high"
|
|
6708
6708
|
}
|
|
6709
6709
|
},
|
|
6710
|
+
"gemini-3.6-flash-high": {
|
|
6711
|
+
"id": "gemini-3.6-flash-high",
|
|
6712
|
+
"name": "Gemini 3.6 Flash (High) (Antigravity)",
|
|
6713
|
+
"api": "google-gemini-cli",
|
|
6714
|
+
"provider": "google-antigravity",
|
|
6715
|
+
"baseUrl": "https://daily-cloudcode-pa.sandbox.googleapis.com",
|
|
6716
|
+
"reasoning": true,
|
|
6717
|
+
"input": [
|
|
6718
|
+
"text",
|
|
6719
|
+
"image"
|
|
6720
|
+
],
|
|
6721
|
+
"cost": {
|
|
6722
|
+
"input": 0,
|
|
6723
|
+
"output": 0,
|
|
6724
|
+
"cacheRead": 0,
|
|
6725
|
+
"cacheWrite": 0
|
|
6726
|
+
},
|
|
6727
|
+
"contextWindow": 1048576,
|
|
6728
|
+
"maxTokens": 65536,
|
|
6729
|
+
"thinking": {
|
|
6730
|
+
"mode": "google-level",
|
|
6731
|
+
"minLevel": "minimal",
|
|
6732
|
+
"maxLevel": "high"
|
|
6733
|
+
}
|
|
6734
|
+
},
|
|
6710
6735
|
"gemini-3-pro-high": {
|
|
6711
6736
|
"id": "gemini-3-pro-high",
|
|
6712
6737
|
"name": "Gemini 3 Pro (High) (Antigravity)",
|
|
@@ -7005,6 +7030,31 @@
|
|
|
7005
7030
|
}
|
|
7006
7031
|
},
|
|
7007
7032
|
"google-vertex": {
|
|
7033
|
+
"gemini-3.6-flash": {
|
|
7034
|
+
"id": "gemini-3.6-flash",
|
|
7035
|
+
"name": "Gemini 3.6 Flash",
|
|
7036
|
+
"api": "google-vertex",
|
|
7037
|
+
"provider": "google-vertex",
|
|
7038
|
+
"baseUrl": "https://{location}-aiplatform.googleapis.com",
|
|
7039
|
+
"reasoning": true,
|
|
7040
|
+
"input": [
|
|
7041
|
+
"text",
|
|
7042
|
+
"image"
|
|
7043
|
+
],
|
|
7044
|
+
"cost": {
|
|
7045
|
+
"input": 0,
|
|
7046
|
+
"output": 0,
|
|
7047
|
+
"cacheRead": 0,
|
|
7048
|
+
"cacheWrite": 0
|
|
7049
|
+
},
|
|
7050
|
+
"contextWindow": 1048576,
|
|
7051
|
+
"maxTokens": 65536,
|
|
7052
|
+
"thinking": {
|
|
7053
|
+
"mode": "google-level",
|
|
7054
|
+
"minLevel": "low",
|
|
7055
|
+
"maxLevel": "high"
|
|
7056
|
+
}
|
|
7057
|
+
},
|
|
7008
7058
|
"gemini-1.5-flash": {
|
|
7009
7059
|
"id": "gemini-1.5-flash",
|
|
7010
7060
|
"name": "Gemini 1.5 Flash",
|
|
@@ -16561,8 +16611,8 @@
|
|
|
16561
16611
|
"maxTokens": 128000,
|
|
16562
16612
|
"thinking": {
|
|
16563
16613
|
"mode": "effort",
|
|
16564
|
-
"minLevel": "
|
|
16565
|
-
"maxLevel": "
|
|
16614
|
+
"minLevel": "low",
|
|
16615
|
+
"maxLevel": "xhigh"
|
|
16566
16616
|
},
|
|
16567
16617
|
"compat": {
|
|
16568
16618
|
"supportsTemperature": false
|
|
@@ -216,7 +216,7 @@ export const PROVIDER_DESCRIPTORS: readonly ProviderDescriptor[] = [
|
|
|
216
216
|
),
|
|
217
217
|
catalogDescriptor(
|
|
218
218
|
"litellm",
|
|
219
|
-
"
|
|
219
|
+
"gpt-5.6-sol",
|
|
220
220
|
config => litellmModelManagerOptions(config),
|
|
221
221
|
catalog("LiteLLM", ["LITELLM_API_KEY"], { allowUnauthenticated: true }),
|
|
222
222
|
),
|
|
@@ -76,7 +76,7 @@ export function getGeminiCliUserAgent(modelId = "gemini-3.1-pro-preview"): strin
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
const ANTIGRAVITY_USER_AGENT = (() => {
|
|
79
|
-
const DEFAULT_ANTIGRAVITY_VERSION = "
|
|
79
|
+
const DEFAULT_ANTIGRAVITY_VERSION = "2.4.3";
|
|
80
80
|
const version = process.env.PI_AI_ANTIGRAVITY_VERSION || DEFAULT_ANTIGRAVITY_VERSION;
|
|
81
81
|
// Map Node.js platform/arch to Antigravity's expected format.
|
|
82
82
|
// Verified against Antigravity source: _qn() and wqn() in main.js.
|
|
@@ -646,6 +646,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
646
646
|
};
|
|
647
647
|
output.stopReason = "stop";
|
|
648
648
|
output.errorMessage = undefined;
|
|
649
|
+
output.rawStopReason = undefined;
|
|
649
650
|
output.timestamp = Date.now();
|
|
650
651
|
started = false;
|
|
651
652
|
};
|
|
@@ -794,6 +795,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
794
795
|
}
|
|
795
796
|
|
|
796
797
|
if (candidate?.finishReason) {
|
|
798
|
+
output.rawStopReason = candidate.finishReason;
|
|
797
799
|
output.stopReason = mapStopReasonString(candidate.finishReason);
|
|
798
800
|
if (output.content.some(b => b.type === "toolCall")) {
|
|
799
801
|
output.stopReason = "toolUse";
|
|
@@ -902,7 +904,9 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
902
904
|
}
|
|
903
905
|
|
|
904
906
|
if (output.stopReason === "aborted" || output.stopReason === "error") {
|
|
905
|
-
throw new Error(
|
|
907
|
+
throw new Error(
|
|
908
|
+
output.rawStopReason ? `Gemini stopped with ${output.rawStopReason}` : "An unknown error occurred",
|
|
909
|
+
);
|
|
906
910
|
}
|
|
907
911
|
|
|
908
912
|
output.duration = Date.now() - startTime;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as os from "node:os";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { $env, $which, isEnoent } from "@f5-sales-demo/pi-utils";
|
|
2
4
|
import {
|
|
3
5
|
type GenerateContentConfig,
|
|
4
6
|
type GenerateContentParameters,
|
|
@@ -6,6 +8,7 @@ import {
|
|
|
6
8
|
type ThinkingConfig,
|
|
7
9
|
ThinkingLevel,
|
|
8
10
|
} from "@google/genai";
|
|
11
|
+
import { $ } from "bun";
|
|
9
12
|
import { calculateCost } from "../models";
|
|
10
13
|
import type {
|
|
11
14
|
Api,
|
|
@@ -41,6 +44,12 @@ export interface GoogleVertexOptions extends StreamOptions {
|
|
|
41
44
|
location?: string;
|
|
42
45
|
}
|
|
43
46
|
|
|
47
|
+
export interface GoogleVertexProjectRuntime {
|
|
48
|
+
readAdcProject(): Promise<string | undefined>;
|
|
49
|
+
findGcloud(): string | null;
|
|
50
|
+
readConfiguredProject(gcloud: string): Promise<string | undefined>;
|
|
51
|
+
}
|
|
52
|
+
|
|
44
53
|
interface GoogleVertexSamplingConfig extends GenerateContentConfig {
|
|
45
54
|
topP?: number;
|
|
46
55
|
topK?: number;
|
|
@@ -94,8 +103,8 @@ export const streamGoogleVertex: StreamFunction<"google-vertex"> = (
|
|
|
94
103
|
|
|
95
104
|
try {
|
|
96
105
|
const apiKey = resolveApiKey(options);
|
|
97
|
-
const project = apiKey ? undefined :
|
|
98
|
-
const location = apiKey ? undefined :
|
|
106
|
+
const project = apiKey ? undefined : await resolveGoogleVertexProject(options);
|
|
107
|
+
const location = apiKey ? undefined : resolveGoogleVertexLocation(options);
|
|
99
108
|
const client = apiKey ? createClientWithApiKey(model, apiKey) : createClient(model, project!, location!);
|
|
100
109
|
const params = buildParams(model, context, options);
|
|
101
110
|
options?.onPayload?.(params);
|
|
@@ -106,7 +115,7 @@ export const streamGoogleVertex: StreamFunction<"google-vertex"> = (
|
|
|
106
115
|
method: "POST",
|
|
107
116
|
url: apiKey
|
|
108
117
|
? `https://aiplatform.googleapis.com/${API_VERSION}/publishers/google/models/${model.id}:streamGenerateContent`
|
|
109
|
-
:
|
|
118
|
+
: googleVertexRequestUrl(model.id, project!, location!),
|
|
110
119
|
body: params,
|
|
111
120
|
};
|
|
112
121
|
const googleStream = await client.models.generateContentStream(params);
|
|
@@ -212,7 +221,7 @@ export const streamGoogleVertex: StreamFunction<"google-vertex"> = (
|
|
|
212
221
|
type: "toolCall",
|
|
213
222
|
id: toolCallId,
|
|
214
223
|
name: part.functionCall.name || "",
|
|
215
|
-
arguments: part.functionCall.args as Record<string,
|
|
224
|
+
arguments: (part.functionCall.args ?? {}) as Record<string, unknown>,
|
|
216
225
|
...(part.thoughtSignature && { thoughtSignature: part.thoughtSignature }),
|
|
217
226
|
};
|
|
218
227
|
|
|
@@ -230,6 +239,7 @@ export const streamGoogleVertex: StreamFunction<"google-vertex"> = (
|
|
|
230
239
|
}
|
|
231
240
|
|
|
232
241
|
if (candidate?.finishReason) {
|
|
242
|
+
output.rawStopReason = candidate.finishReason;
|
|
233
243
|
output.stopReason = mapStopReason(candidate.finishReason);
|
|
234
244
|
if (output.content.some(b => b.type === "toolCall")) {
|
|
235
245
|
output.stopReason = "toolUse";
|
|
@@ -285,7 +295,9 @@ export const streamGoogleVertex: StreamFunction<"google-vertex"> = (
|
|
|
285
295
|
}
|
|
286
296
|
|
|
287
297
|
if (output.stopReason === "aborted" || output.stopReason === "error") {
|
|
288
|
-
throw new Error(
|
|
298
|
+
throw new Error(
|
|
299
|
+
output.rawStopReason ? `Gemini stopped with ${output.rawStopReason}` : "An unknown error occurred",
|
|
300
|
+
);
|
|
289
301
|
}
|
|
290
302
|
|
|
291
303
|
output.duration = Date.now() - startTime;
|
|
@@ -345,22 +357,64 @@ function resolveApiKey(options?: GoogleVertexOptions): string | undefined {
|
|
|
345
357
|
return realKey || $env.GOOGLE_CLOUD_API_KEY;
|
|
346
358
|
}
|
|
347
359
|
|
|
348
|
-
|
|
360
|
+
const defaultProjectRuntime: GoogleVertexProjectRuntime = {
|
|
361
|
+
readAdcProject,
|
|
362
|
+
findGcloud: () => $which("gcloud"),
|
|
363
|
+
readConfiguredProject: async gcloud => {
|
|
364
|
+
const result = await $`${gcloud} config get-value project`.quiet().nothrow();
|
|
365
|
+
const configuredProject = result.text().trim();
|
|
366
|
+
if (result.exitCode !== 0 || !configuredProject || configuredProject === "(unset)") {
|
|
367
|
+
return undefined;
|
|
368
|
+
}
|
|
369
|
+
return configuredProject;
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
export async function resolveGoogleVertexProject(
|
|
374
|
+
options?: GoogleVertexOptions,
|
|
375
|
+
runtime: GoogleVertexProjectRuntime = defaultProjectRuntime,
|
|
376
|
+
): Promise<string> {
|
|
349
377
|
const project = options?.project || $env.GOOGLE_CLOUD_PROJECT || $env.GCLOUD_PROJECT;
|
|
350
|
-
if (
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
378
|
+
if (project) return project;
|
|
379
|
+
|
|
380
|
+
const adcProject = await runtime.readAdcProject();
|
|
381
|
+
if (adcProject) return adcProject;
|
|
382
|
+
|
|
383
|
+
const gcloud = runtime.findGcloud();
|
|
384
|
+
if (gcloud) {
|
|
385
|
+
const configuredProject = await runtime.readConfiguredProject(gcloud);
|
|
386
|
+
if (configuredProject) return configuredProject;
|
|
354
387
|
}
|
|
355
|
-
|
|
388
|
+
|
|
389
|
+
throw new Error(
|
|
390
|
+
"Vertex AI requires a project ID. Set GOOGLE_CLOUD_PROJECT/GCLOUD_PROJECT, run `gcloud config set project PROJECT_ID`, or pass project in options.",
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export function resolveGoogleVertexLocation(options?: GoogleVertexOptions): string {
|
|
395
|
+
return options?.location || $env.GOOGLE_CLOUD_LOCATION || "global";
|
|
356
396
|
}
|
|
357
397
|
|
|
358
|
-
function
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
398
|
+
async function readAdcProject(): Promise<string | undefined> {
|
|
399
|
+
const credentialsPath =
|
|
400
|
+
$env.GOOGLE_APPLICATION_CREDENTIALS ||
|
|
401
|
+
path.join(os.homedir(), ".config", "gcloud", "application_default_credentials.json");
|
|
402
|
+
try {
|
|
403
|
+
const credentials = (await Bun.file(credentialsPath).json()) as {
|
|
404
|
+
project_id?: unknown;
|
|
405
|
+
};
|
|
406
|
+
if (typeof credentials.project_id === "string" && credentials.project_id.length > 0) {
|
|
407
|
+
return credentials.project_id;
|
|
408
|
+
}
|
|
409
|
+
} catch (error) {
|
|
410
|
+
if (!isEnoent(error)) throw error;
|
|
362
411
|
}
|
|
363
|
-
return
|
|
412
|
+
return undefined;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function googleVertexRequestUrl(modelId: string, project: string, location: string): string {
|
|
416
|
+
const host = location === "global" ? "aiplatform.googleapis.com" : `${location}-aiplatform.googleapis.com`;
|
|
417
|
+
return `https://${host}/${API_VERSION}/projects/${project}/locations/${location}/publishers/google/models/${modelId}:streamGenerateContent`;
|
|
364
418
|
}
|
|
365
419
|
|
|
366
420
|
function buildParams(
|
package/src/providers/google.ts
CHANGED
|
@@ -192,7 +192,7 @@ export const streamGoogle: StreamFunction<"google-generative-ai"> = (
|
|
|
192
192
|
type: "toolCall",
|
|
193
193
|
id: toolCallId,
|
|
194
194
|
name: part.functionCall.name || "",
|
|
195
|
-
arguments: (part.functionCall.args ?? {}) as Record<string,
|
|
195
|
+
arguments: (part.functionCall.args ?? {}) as Record<string, unknown>,
|
|
196
196
|
...(part.thoughtSignature && { thoughtSignature: part.thoughtSignature }),
|
|
197
197
|
};
|
|
198
198
|
|
|
@@ -210,6 +210,7 @@ export const streamGoogle: StreamFunction<"google-generative-ai"> = (
|
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
if (candidate?.finishReason) {
|
|
213
|
+
output.rawStopReason = candidate.finishReason;
|
|
213
214
|
output.stopReason = mapStopReason(candidate.finishReason);
|
|
214
215
|
if (output.content.some(b => b.type === "toolCall")) {
|
|
215
216
|
output.stopReason = "toolUse";
|
|
@@ -265,7 +266,9 @@ export const streamGoogle: StreamFunction<"google-generative-ai"> = (
|
|
|
265
266
|
}
|
|
266
267
|
|
|
267
268
|
if (output.stopReason === "aborted" || output.stopReason === "error") {
|
|
268
|
-
throw new Error(
|
|
269
|
+
throw new Error(
|
|
270
|
+
output.rawStopReason ? `Gemini stopped with ${output.rawStopReason}` : "An unknown error occurred",
|
|
271
|
+
);
|
|
269
272
|
}
|
|
270
273
|
|
|
271
274
|
output.duration = Date.now() - startTime;
|
package/src/stream.ts
CHANGED
|
@@ -102,10 +102,7 @@ const serviceProviderMap: Record<string, KeyResolver> = {
|
|
|
102
102
|
if ($env.GOOGLE_CLOUD_API_KEY) {
|
|
103
103
|
return $env.GOOGLE_CLOUD_API_KEY;
|
|
104
104
|
}
|
|
105
|
-
|
|
106
|
-
const hasProject = !!($env.GOOGLE_CLOUD_PROJECT || $env.GCLOUD_PROJECT);
|
|
107
|
-
const hasLocation = !!$env.GOOGLE_CLOUD_LOCATION;
|
|
108
|
-
if (hasCredentials && hasProject && hasLocation) {
|
|
105
|
+
if (hasVertexAdcCredentials()) {
|
|
109
106
|
return "<authenticated>";
|
|
110
107
|
}
|
|
111
108
|
},
|
package/src/types.ts
CHANGED
|
@@ -366,6 +366,8 @@ export interface AssistantMessage {
|
|
|
366
366
|
usage: Usage;
|
|
367
367
|
stopReason: StopReason;
|
|
368
368
|
errorMessage?: string;
|
|
369
|
+
/** Provider-native completion reason retained for diagnostics and transport-specific recovery. */
|
|
370
|
+
rawStopReason?: string;
|
|
369
371
|
/** Provider-specific opaque payload used to reconstruct transport-native history. */
|
|
370
372
|
providerPayload?: ProviderPayload;
|
|
371
373
|
timestamp: number; // Unix timestamp in milliseconds
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
buildAnthropicHeaders as buildProviderAnthropicHeaders,
|
|
17
17
|
normalizeAnthropicBaseUrl,
|
|
18
18
|
} from "../providers/anthropic";
|
|
19
|
-
import { getEnvApiKey } from "../stream";
|
|
20
19
|
|
|
21
20
|
/** Auth configuration for Anthropic */
|
|
22
21
|
export interface AnthropicAuthConfig {
|
|
@@ -25,6 +24,14 @@ export interface AnthropicAuthConfig {
|
|
|
25
24
|
isOAuth: boolean;
|
|
26
25
|
}
|
|
27
26
|
|
|
27
|
+
export type AnthropicAuthEnvironment = Readonly<Record<string, string | undefined>>;
|
|
28
|
+
|
|
29
|
+
export interface FindAnthropicAuthOptions {
|
|
30
|
+
store?: AuthCredentialStore;
|
|
31
|
+
environment?: AnthropicAuthEnvironment;
|
|
32
|
+
modelsYmlPath?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
28
35
|
/** OAuth credential for Anthropic API access */
|
|
29
36
|
export interface AnthropicOAuthCredential {
|
|
30
37
|
type: "oauth";
|
|
@@ -36,8 +43,8 @@ export interface AnthropicOAuthCredential {
|
|
|
36
43
|
|
|
37
44
|
const DEFAULT_BASE_URL = "https://api.anthropic.com";
|
|
38
45
|
|
|
39
|
-
function isFoundryEnabled(): boolean {
|
|
40
|
-
const value =
|
|
46
|
+
function isFoundryEnabled(environment: AnthropicAuthEnvironment): boolean {
|
|
47
|
+
const value = environment.CLAUDE_CODE_USE_FOUNDRY;
|
|
41
48
|
if (!value) return false;
|
|
42
49
|
const normalized = value.trim().toLowerCase();
|
|
43
50
|
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
|
|
@@ -47,15 +54,24 @@ function normalizeBaseUrl(baseUrl: string | undefined): string | undefined {
|
|
|
47
54
|
const trimmed = baseUrl?.trim();
|
|
48
55
|
return trimmed ? trimmed.replace(/\/+$/, "") : undefined;
|
|
49
56
|
}
|
|
50
|
-
function resolveAnthropicBaseUrlFromEnv(): string | undefined {
|
|
51
|
-
if (isFoundryEnabled()) {
|
|
52
|
-
const foundryBaseUrl = normalizeBaseUrl(
|
|
57
|
+
function resolveAnthropicBaseUrlFromEnv(environment: AnthropicAuthEnvironment): string | undefined {
|
|
58
|
+
if (isFoundryEnabled(environment)) {
|
|
59
|
+
const foundryBaseUrl = normalizeBaseUrl(environment.FOUNDRY_BASE_URL);
|
|
53
60
|
if (foundryBaseUrl) return foundryBaseUrl;
|
|
54
61
|
}
|
|
55
|
-
const anthropicBaseUrl = normalizeBaseUrl(
|
|
62
|
+
const anthropicBaseUrl = normalizeBaseUrl(environment.ANTHROPIC_BASE_URL);
|
|
56
63
|
return anthropicBaseUrl || undefined;
|
|
57
64
|
}
|
|
58
65
|
|
|
66
|
+
function resolveGenericAnthropicApiKey(environment: AnthropicAuthEnvironment): string | undefined {
|
|
67
|
+
if (isFoundryEnabled(environment)) {
|
|
68
|
+
return (
|
|
69
|
+
environment.ANTHROPIC_FOUNDRY_API_KEY ?? environment.ANTHROPIC_OAUTH_TOKEN ?? environment.ANTHROPIC_API_KEY
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return environment.ANTHROPIC_OAUTH_TOKEN ?? environment.ANTHROPIC_API_KEY;
|
|
73
|
+
}
|
|
74
|
+
|
|
59
75
|
/**
|
|
60
76
|
* Checks if a token is an OAuth token by looking for sk-ant-oat prefix.
|
|
61
77
|
* @param apiKey - The API key to check
|
|
@@ -112,14 +128,17 @@ async function readAnthropicOAuthCredentials(store?: AuthCredentialStore): Promi
|
|
|
112
128
|
* Skips shell-backed secrets, which this tier cannot resolve.
|
|
113
129
|
* @returns AnthropicAuthConfig or null if unavailable
|
|
114
130
|
*/
|
|
115
|
-
function readAnthropicAuthFromModelsYml(
|
|
116
|
-
|
|
131
|
+
function readAnthropicAuthFromModelsYml(
|
|
132
|
+
environment: AnthropicAuthEnvironment,
|
|
133
|
+
modelsYmlPath?: string,
|
|
134
|
+
): AnthropicAuthConfig | null {
|
|
135
|
+
const block = readProviderFromModelsYml("anthropic", modelsYmlPath);
|
|
117
136
|
if (!block?.baseUrl || !block.apiKey) return null;
|
|
118
137
|
|
|
119
138
|
let apiKey: string | undefined;
|
|
120
139
|
switch (block.apiKey.kind) {
|
|
121
140
|
case "envVar":
|
|
122
|
-
apiKey =
|
|
141
|
+
apiKey = environment[block.apiKey.name];
|
|
123
142
|
break;
|
|
124
143
|
case "literal":
|
|
125
144
|
apiKey = block.apiKey.value;
|
|
@@ -159,13 +178,16 @@ export function normalizeLitellmBase(url: string): string {
|
|
|
159
178
|
* 5. ANTHROPIC_API_KEY / ANTHROPIC_BASE_URL fallback
|
|
160
179
|
* 6. Anthropic provider credentials from models.yml
|
|
161
180
|
* 7. LiteLLM passthrough (LITELLM_BASE_URL / LITELLM_API_KEY)
|
|
162
|
-
* @param
|
|
181
|
+
* @param options - Injectable auth inputs. Production callers normally omit this;
|
|
182
|
+
* tests and embedded callers can provide an isolated environment, models path,
|
|
183
|
+
* and credential store without mutating process-global state.
|
|
163
184
|
* @returns The first valid auth configuration found, or null if none available
|
|
164
185
|
*/
|
|
165
|
-
export async function findAnthropicAuth(
|
|
186
|
+
export async function findAnthropicAuth(options: FindAnthropicAuthOptions = {}): Promise<AnthropicAuthConfig | null> {
|
|
187
|
+
const environment = options.environment ?? $env;
|
|
166
188
|
// 1. Explicit search-specific env vars
|
|
167
|
-
const searchApiKey =
|
|
168
|
-
const searchBaseUrl =
|
|
189
|
+
const searchApiKey = environment.ANTHROPIC_SEARCH_API_KEY;
|
|
190
|
+
const searchBaseUrl = environment.ANTHROPIC_SEARCH_BASE_URL;
|
|
169
191
|
if (searchApiKey) {
|
|
170
192
|
return {
|
|
171
193
|
apiKey: searchApiKey,
|
|
@@ -175,11 +197,11 @@ export async function findAnthropicAuth(store?: AuthCredentialStore): Promise<An
|
|
|
175
197
|
}
|
|
176
198
|
|
|
177
199
|
// 2. Foundry explicit env override
|
|
178
|
-
const foundryApiKey = isFoundryEnabled() ?
|
|
200
|
+
const foundryApiKey = isFoundryEnabled(environment) ? environment.ANTHROPIC_FOUNDRY_API_KEY?.trim() : undefined;
|
|
179
201
|
if (foundryApiKey) {
|
|
180
202
|
return {
|
|
181
203
|
apiKey: foundryApiKey,
|
|
182
|
-
baseUrl: resolveAnthropicBaseUrlFromEnv() ?? DEFAULT_BASE_URL,
|
|
204
|
+
baseUrl: resolveAnthropicBaseUrlFromEnv(environment) ?? DEFAULT_BASE_URL,
|
|
183
205
|
isOAuth: isOAuthToken(foundryApiKey),
|
|
184
206
|
};
|
|
185
207
|
}
|
|
@@ -189,8 +211,8 @@ export async function findAnthropicAuth(store?: AuthCredentialStore): Promise<An
|
|
|
189
211
|
// env-var fallback tiers from running.
|
|
190
212
|
let storeError: unknown;
|
|
191
213
|
try {
|
|
192
|
-
const ownsStore = !store;
|
|
193
|
-
const effectiveStore = store ?? (await AuthCredentialStore.open(getAgentDbPath()));
|
|
214
|
+
const ownsStore = !options.store;
|
|
215
|
+
const effectiveStore = options.store ?? (await AuthCredentialStore.open(getAgentDbPath()));
|
|
194
216
|
try {
|
|
195
217
|
// 3. OAuth credentials in agent.db (with 5-minute expiry buffer)
|
|
196
218
|
const expiryBuffer = 5 * 60 * 1000; // 5 minutes
|
|
@@ -212,7 +234,7 @@ export async function findAnthropicAuth(store?: AuthCredentialStore): Promise<An
|
|
|
212
234
|
if (storedApiKey) {
|
|
213
235
|
return {
|
|
214
236
|
apiKey: storedApiKey,
|
|
215
|
-
baseUrl: resolveAnthropicBaseUrlFromEnv() ?? DEFAULT_BASE_URL,
|
|
237
|
+
baseUrl: resolveAnthropicBaseUrlFromEnv(environment) ?? DEFAULT_BASE_URL,
|
|
216
238
|
isOAuth: isOAuthToken(storedApiKey),
|
|
217
239
|
};
|
|
218
240
|
}
|
|
@@ -228,8 +250,8 @@ export async function findAnthropicAuth(store?: AuthCredentialStore): Promise<An
|
|
|
228
250
|
}
|
|
229
251
|
|
|
230
252
|
// 5. Generic ANTHROPIC_API_KEY fallback
|
|
231
|
-
const apiKey =
|
|
232
|
-
const baseUrl = resolveAnthropicBaseUrlFromEnv();
|
|
253
|
+
const apiKey = resolveGenericAnthropicApiKey(environment);
|
|
254
|
+
const baseUrl = resolveAnthropicBaseUrlFromEnv(environment);
|
|
233
255
|
if (apiKey) {
|
|
234
256
|
return {
|
|
235
257
|
apiKey,
|
|
@@ -240,15 +262,15 @@ export async function findAnthropicAuth(store?: AuthCredentialStore): Promise<An
|
|
|
240
262
|
|
|
241
263
|
// 6. Anthropic provider credentials from models.yml
|
|
242
264
|
try {
|
|
243
|
-
const modelsYmlAuth = readAnthropicAuthFromModelsYml();
|
|
265
|
+
const modelsYmlAuth = readAnthropicAuthFromModelsYml(environment, options.modelsYmlPath);
|
|
244
266
|
if (modelsYmlAuth) return modelsYmlAuth;
|
|
245
267
|
} catch {
|
|
246
268
|
/* Best-effort — don't block env-var fallback */
|
|
247
269
|
}
|
|
248
270
|
|
|
249
271
|
// 7. Derive from litellm passthrough credentials
|
|
250
|
-
const litellmBaseUrl =
|
|
251
|
-
const litellmApiKey =
|
|
272
|
+
const litellmBaseUrl = environment.LITELLM_BASE_URL;
|
|
273
|
+
const litellmApiKey = environment.LITELLM_API_KEY;
|
|
252
274
|
if (litellmBaseUrl && litellmApiKey) {
|
|
253
275
|
return {
|
|
254
276
|
apiKey: litellmApiKey,
|