@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 CHANGED
@@ -1,6 +1,25 @@
1
+ <!-- markdownlint-configure-file { "MD024": { "siblings_only": true } } -->
2
+
1
3
  # Changelog
2
4
 
3
- ## [Unreleased]
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.2.7",
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
- "@google/genai": "^2.13",
48
- "@f5-sales-demo/pi-utils": "20.2.7",
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": "high",
16565
- "maxLevel": "high"
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
- "claude-opus-4-6",
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 = "1.104.0";
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("An unknown error occurred");
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 { $env } from "@f5-sales-demo/pi-utils";
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 : resolveProject(options);
98
- const location = apiKey ? undefined : resolveLocation(options);
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
- : `https://${location}-aiplatform.googleapis.com/${API_VERSION}/projects/${project}/locations/${location}/publishers/google/models/${model.id}:streamGenerateContent`,
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, any>,
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("An unknown error occurred");
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
- function resolveProject(options?: GoogleVertexOptions): string {
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 (!project) {
351
- throw new Error(
352
- "Vertex AI requires a project ID. Set GOOGLE_CLOUD_PROJECT/GCLOUD_PROJECT or pass project in options.",
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
- return project;
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 resolveLocation(options?: GoogleVertexOptions): string {
359
- const location = options?.location || $env.GOOGLE_CLOUD_LOCATION;
360
- if (!location) {
361
- throw new Error("Vertex AI requires a location. Set GOOGLE_CLOUD_LOCATION or pass location in options.");
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 location;
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(
@@ -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, any>,
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("An unknown error occurred");
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
- const hasCredentials = hasVertexAdcCredentials();
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 = $env.CLAUDE_CODE_USE_FOUNDRY;
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($env.FOUNDRY_BASE_URL);
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($env.ANTHROPIC_BASE_URL);
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(): AnthropicAuthConfig | null {
116
- const block = readProviderFromModelsYml("anthropic");
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 = process.env[block.apiKey.name];
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 store - Optional credential store (creates one from default db path if not provided)
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(store?: AuthCredentialStore): Promise<AnthropicAuthConfig | null> {
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 = $env.ANTHROPIC_SEARCH_API_KEY;
168
- const searchBaseUrl = $env.ANTHROPIC_SEARCH_BASE_URL;
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() ? $env.ANTHROPIC_FOUNDRY_API_KEY?.trim() : undefined;
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 = getEnvApiKey("anthropic");
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 = $env.LITELLM_BASE_URL;
251
- const litellmApiKey = $env.LITELLM_API_KEY;
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,