feat: remove Google Vertex AI provider support

This commit is contained in:
ezl-keygraph
2026-06-15 12:49:40 +05:30
parent 1908156525
commit a9e966026c
16 changed files with 25 additions and 280 deletions
+1 -74
View File
@@ -5,7 +5,6 @@
* then persists everything to ~/.shannon/config.toml with 0o600 permissions.
*/
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import * as p from '@clack/prompts';
@@ -13,7 +12,7 @@ import { type ShannonConfig, saveConfig } from '../config/writer.js';
const SHANNON_HOME = path.join(os.homedir(), '.shannon');
type Provider = 'anthropic' | 'custom_base_url' | 'bedrock' | 'vertex';
type Provider = 'anthropic' | 'custom_base_url' | 'bedrock';
export async function setup(): Promise<void> {
p.intro('Shannon Setup');
@@ -25,7 +24,6 @@ export async function setup(): Promise<void> {
{ value: 'anthropic' as const, label: 'Claude Direct', hint: 'recommended' },
{ value: 'custom_base_url' as const, label: 'Custom Base URL', hint: 'proxies, gateways' },
{ value: 'bedrock' as const, label: 'Claude via AWS Bedrock' },
{ value: 'vertex' as const, label: 'Claude via Google Vertex AI' },
],
});
if (p.isCancel(provider)) return cancelAndExit();
@@ -51,8 +49,6 @@ async function setupProvider(provider: Provider): Promise<ShannonConfig> {
return setupCustomBaseUrl();
case 'bedrock':
return setupBedrock();
case 'vertex':
return setupVertex();
}
}
@@ -213,75 +209,6 @@ async function setupBedrock(): Promise<ShannonConfig> {
};
}
async function setupVertex(): Promise<ShannonConfig> {
// 1. Collect region and project ID
const region = await p.text({
message: 'Google Cloud region',
placeholder: 'us-east5',
validate: required('Region is required'),
});
if (p.isCancel(region)) return cancelAndExit();
const projectId = await p.text({
message: 'GCP Project ID',
validate: required('Project ID is required'),
});
if (p.isCancel(projectId)) return cancelAndExit();
// 2. File picker for service account key
p.log.info('Select the path to your GCP Service Account JSON key file.');
const keySourcePath = await p.path({
message: 'Service Account JSON key file',
validate: (value) => {
if (!value) return 'Path is required';
if (!fs.existsSync(value)) return 'File not found';
if (!value.endsWith('.json')) return 'Must be a .json file';
return undefined;
},
});
if (p.isCancel(keySourcePath)) return cancelAndExit();
// 3. Copy key to ~/.shannon/ and lock permissions
const destPath = path.join(SHANNON_HOME, 'google-sa-key.json');
fs.mkdirSync(SHANNON_HOME, { recursive: true });
fs.copyFileSync(keySourcePath, destPath);
fs.chmodSync(destPath, 0o600);
p.log.success(`Key copied to ${destPath} (permissions: 0600)`);
// 4. Model tiers
const models = await p.group({
small: () =>
p.text({
message: 'Small model ID',
placeholder: 'claude-haiku-4-5@20251001',
validate: required('Small model ID is required'),
}),
medium: () =>
p.text({
message: 'Medium model ID',
placeholder: 'claude-sonnet-4-6',
validate: required('Medium model ID is required'),
}),
large: () =>
p.text({
message: 'Large model ID',
placeholder: 'claude-opus-4-8',
validate: required('Large model ID is required'),
}),
});
if (p.isCancel(models)) return cancelAndExit();
return {
vertex: {
use: true,
region,
project_id: projectId,
key_path: destPath,
},
models: { small: models.small, medium: models.medium, large: models.large },
};
}
// === Helpers ===
async function maybePromptAdaptiveThinking(config: ShannonConfig): Promise<void> {
+1 -9
View File
@@ -10,7 +10,7 @@ import fs from 'node:fs';
import path from 'node:path';
import { ensureImage, ensureInfra, randomSuffix, spawnWorker } from '../docker.js';
import { buildEnvFlags, loadEnv, validateCredentials } from '../env.js';
import { getCredentialsPath, getWorkspacesDir, initHome } from '../home.js';
import { getWorkspacesDir, initHome } from '../home.js';
import { isLocal } from '../mode.js';
import { resolveConfig, resolveRepo } from '../paths.js';
import { displaySplash } from '../splash.js';
@@ -78,13 +78,6 @@ export async function start(args: StartArgs): Promise<void> {
}
fs.mkdirSync(path.join(repo.hostPath, '.playwright'), { recursive: true });
const credentialsPath = getCredentialsPath();
const hasCredentials = fs.existsSync(credentialsPath);
if (hasCredentials) {
process.env.GOOGLE_APPLICATION_CREDENTIALS = '/app/credentials/google-sa-key.json';
}
// 10. Resolve output directory
const outputDir = args.output ? path.resolve(args.output) : undefined;
if (outputDir) {
@@ -107,7 +100,6 @@ export async function start(args: StartArgs): Promise<void> {
containerName,
envFlags: buildEnvFlags(),
...(config && { config }),
...(hasCredentials && { credentials: credentialsPath }),
...(promptsDir && { promptsDir }),
...(outputDir && { outputDir }),
workspace,
+2 -18
View File
@@ -36,12 +36,6 @@ const CONFIG_MAP: readonly ConfigMapping[] = [
{ env: 'AWS_REGION', toml: 'bedrock.region', type: 'string' },
{ env: 'AWS_BEARER_TOKEN_BEDROCK', toml: 'bedrock.token', type: 'string' },
// Vertex
{ env: 'CLAUDE_CODE_USE_VERTEX', toml: 'vertex.use', type: 'boolean' },
{ env: 'CLOUD_ML_REGION', toml: 'vertex.region', type: 'string' },
{ env: 'ANTHROPIC_VERTEX_PROJECT_ID', toml: 'vertex.project_id', type: 'string' },
{ env: 'GOOGLE_APPLICATION_CREDENTIALS', toml: 'vertex.key_path', type: 'string' },
// Custom Base URL
{ env: 'ANTHROPIC_BASE_URL', toml: 'custom_base_url.base_url', type: 'string' },
{ env: 'ANTHROPIC_AUTH_TOKEN', toml: 'custom_base_url.auth_token', type: 'string' },
@@ -154,20 +148,10 @@ function validateProviderFields(config: TOMLConfig, provider: string, errors: st
validateModelTiers(config, 'bedrock', errors);
break;
}
case 'vertex': {
const required = ['use', 'region', 'project_id', 'key_path'];
const missing = required.filter((k) => !keys.includes(k));
if (missing.length > 0) {
errors.push(`[vertex] missing required keys: ${missing.join(', ')}`);
}
validateModelTiers(config, 'vertex', errors);
break;
}
}
}
/** Bedrock and Vertex require a [models] section with all three tiers. */
/** Bedrock requires a [models] section with all three tiers. */
function validateModelTiers(config: TOMLConfig, provider: string, errors: string[]): void {
const models = config.models as Record<string, unknown> | undefined;
if (!models || typeof models !== 'object') {
@@ -227,7 +211,7 @@ function validateConfig(config: TOMLConfig): string[] {
}
// 4. Only one provider section allowed (ignore empty sections)
const PROVIDER_SECTIONS = ['anthropic', 'custom_base_url', 'bedrock', 'vertex'] as const;
const PROVIDER_SECTIONS = ['anthropic', 'custom_base_url', 'bedrock'] as const;
const present = PROVIDER_SECTIONS.filter((s) => {
const section = config[s];
return section && typeof section === 'object' && Object.keys(section).length > 0;
-1
View File
@@ -12,7 +12,6 @@ export interface ShannonConfig {
anthropic?: { api_key?: string; oauth_token?: string };
custom_base_url?: { base_url?: string; auth_token?: string };
bedrock?: { use?: boolean; region?: string; token?: string };
vertex?: { use?: boolean; region?: string; project_id?: string; key_path?: string };
models?: { small?: string; medium?: string; large?: string };
}
-6
View File
@@ -236,7 +236,6 @@ export interface WorkerOptions {
containerName: string;
envFlags: string[];
config?: { hostPath: string; containerPath: string };
credentials?: string;
promptsDir?: string;
outputDir?: string;
workspace: string;
@@ -291,11 +290,6 @@ export function spawnWorker(opts: WorkerOptions): ChildProcess {
args.push('-v', `${opts.outputDir}:/app/output`);
}
// Mount credentials file to fixed container path
if (opts.credentials) {
args.push('-v', `${opts.credentials}:/app/credentials/google-sa-key.json:ro`);
}
// Environment
args.push(...opts.envFlags);
+1 -29
View File
@@ -18,10 +18,6 @@ const FORWARD_VARS = [
'CLAUDE_CODE_USE_BEDROCK',
'AWS_REGION',
'AWS_BEARER_TOKEN_BEDROCK',
'CLAUDE_CODE_USE_VERTEX',
'CLOUD_ML_REGION',
'ANTHROPIC_VERTEX_PROJECT_ID',
'GOOGLE_APPLICATION_CREDENTIALS',
'ANTHROPIC_SMALL_MODEL',
'ANTHROPIC_MEDIUM_MODEL',
'ANTHROPIC_LARGE_MODEL',
@@ -62,7 +58,7 @@ export function buildEnvFlags(): string[] {
interface CredentialValidation {
valid: boolean;
error?: string;
mode: 'api-key' | 'oauth' | 'custom-base-url' | 'bedrock' | 'vertex';
mode: 'api-key' | 'oauth' | 'custom-base-url' | 'bedrock';
}
/** Check if a custom Anthropic-compatible base URL is configured. */
@@ -77,7 +73,6 @@ function detectProviders(): string[] {
if (process.env.CLAUDE_CODE_OAUTH_TOKEN) providers.push('Anthropic OAuth');
if (isCustomBaseUrlConfigured()) providers.push('Custom Base URL');
if (process.env.CLAUDE_CODE_USE_BEDROCK === '1') providers.push('AWS Bedrock');
if (process.env.CLAUDE_CODE_USE_VERTEX === '1') providers.push('Google Vertex');
return providers;
}
@@ -120,29 +115,6 @@ export function validateCredentials(): CredentialValidation {
}
return { valid: true, mode: 'bedrock' };
}
if (process.env.CLAUDE_CODE_USE_VERTEX === '1') {
const missing: string[] = [];
if (!process.env.CLOUD_ML_REGION) missing.push('CLOUD_ML_REGION');
if (!process.env.ANTHROPIC_VERTEX_PROJECT_ID) missing.push('ANTHROPIC_VERTEX_PROJECT_ID');
if (!process.env.ANTHROPIC_SMALL_MODEL) missing.push('ANTHROPIC_SMALL_MODEL');
if (!process.env.ANTHROPIC_MEDIUM_MODEL) missing.push('ANTHROPIC_MEDIUM_MODEL');
if (!process.env.ANTHROPIC_LARGE_MODEL) missing.push('ANTHROPIC_LARGE_MODEL');
if (missing.length > 0) {
return {
valid: false,
mode: 'vertex',
error: `Vertex AI mode requires: ${missing.join(', ')}`,
};
}
if (!process.env.GOOGLE_APPLICATION_CREDENTIALS) {
return {
valid: false,
mode: 'vertex',
error: 'Vertex AI mode requires GOOGLE_APPLICATION_CREDENTIALS',
};
}
return { valid: true, mode: 'vertex' };
}
const hint =
getMode() === 'local'
+2 -20
View File
@@ -1,7 +1,7 @@
/**
* Shannon state directory management.
*
* Local mode (cloned repo): uses ./workspaces/, ./credentials/
* Local mode (cloned repo): uses ./workspaces/
* NPX mode: uses ~/.shannon/workspaces/, ~/.shannon/
*/
@@ -20,32 +20,14 @@ export function getWorkspacesDir(): string {
return getMode() === 'local' ? path.resolve('workspaces') : path.join(SHANNON_HOME, 'workspaces');
}
/**
* Resolve the Vertex credentials file path.
*
* Checks GOOGLE_APPLICATION_CREDENTIALS env var first (may be set by TOML resolver),
* then falls back to mode-appropriate default location.
*/
export function getCredentialsPath(): string {
const envPath = process.env.GOOGLE_APPLICATION_CREDENTIALS;
if (envPath && fs.existsSync(envPath)) return path.resolve(envPath);
if (getMode() === 'local') {
return path.resolve('credentials', 'google-sa-key.json');
}
return path.join(SHANNON_HOME, 'google-sa-key.json');
}
/**
* Initialize state directories.
* Local mode: creates ./workspaces/ and ./credentials/
* Local mode: creates ./workspaces/
* NPX mode: creates ~/.shannon/workspaces/
*/
export function initHome(): void {
if (getMode() === 'local') {
fs.mkdirSync(path.resolve('workspaces'), { recursive: true });
fs.mkdirSync(path.resolve('credentials'), { recursive: true });
} else {
fs.mkdirSync(path.join(SHANNON_HOME, 'workspaces'), { recursive: true });
}
+5 -7
View File
@@ -14,12 +14,12 @@
*
* Users override per tier via ANTHROPIC_SMALL_MODEL / ANTHROPIC_MEDIUM_MODEL /
* ANTHROPIC_LARGE_MODEL, which works across all providers (Anthropic, Bedrock,
* Vertex, custom base URL).
* custom base URL).
*
* Resolution returns a pi `Model` object via `ModelRegistry.find`, plus the
* `thinkingLevel` and an `AuthStorage` primed with runtime credentials. Bedrock
* and Vertex authenticate from the process environment (the AWS_ and GOOGLE_ vars
* the CLI forwards), so they need no runtime API key.
* authenticates from the process environment (the AWS_ vars the CLI forwards), so
* it needs no runtime API key.
*/
import type { ThinkingLevel } from '@earendil-works/pi-agent-core';
@@ -40,8 +40,6 @@ function piProviderId(providerConfig?: ProviderConfig): string {
switch (providerConfig?.providerType) {
case 'bedrock':
return 'amazon-bedrock';
case 'vertex':
return 'google-vertex';
default:
// 'anthropic_api', 'custom_base_url', or unset all resolve to the anthropic
// provider; custom_base_url overrides baseUrl/auth below.
@@ -94,8 +92,8 @@ export interface ModelSelection {
* → ANTHROPIC_API_KEY env. OAuth (CLAUDE_CODE_OAUTH_TOKEN) is read from env by pi.
* - Custom base URL (custom_base_url): the auth token is set as the anthropic
* runtime key and the model's baseUrl is overridden.
* - Bedrock / Vertex: authenticate from the process environment (the AWS_ and
* GOOGLE_ vars), no runtime key needed.
* - Bedrock: authenticates from the process environment (the AWS_ vars), no
* runtime key needed.
*/
export function resolveModelSelection(
registryFactory: (authStorage: AuthStorage) => ModelRegistry,
+5 -56
View File
@@ -15,7 +15,7 @@
* 1. Repository path exists and contains .git
* 2. Config file parses and validates (if provided)
* 3. code_path rules match real entries in the repo (filesystem only)
* 4. Credentials validate via Claude Agent SDK query (API key, OAuth, Bedrock, or Vertex AI)
* 4. Credentials validate via a minimal pi session (API key, OAuth, or Bedrock)
* 5. Target URL resolves, is not link-local (cloud metadata), and is reachable (DNS + HTTP)
*/
@@ -405,62 +405,11 @@ async function validateCredentials(
return ok(undefined);
}
// 3. Vertex AI mode — validate required GCP credentials are present
if (process.env.CLAUDE_CODE_USE_VERTEX === '1') {
const required = [
'CLOUD_ML_REGION',
'ANTHROPIC_VERTEX_PROJECT_ID',
'ANTHROPIC_SMALL_MODEL',
'ANTHROPIC_MEDIUM_MODEL',
'ANTHROPIC_LARGE_MODEL',
];
const missing = required.filter((v) => !process.env[v]);
if (missing.length > 0) {
return err(
new PentestError(
`Vertex AI mode requires the following env vars in .env: ${missing.join(', ')}`,
'config',
false,
{ missing },
ErrorCode.AUTH_FAILED,
),
);
}
// Validate service account credentials file is accessible
const credPath = process.env.GOOGLE_APPLICATION_CREDENTIALS;
if (!credPath) {
return err(
new PentestError(
'Vertex AI mode requires GOOGLE_APPLICATION_CREDENTIALS pointing to a service account key JSON file',
'config',
false,
{},
ErrorCode.AUTH_FAILED,
),
);
}
try {
await fs.access(credPath);
} catch {
return err(
new PentestError(
`Service account key file not found at: ${credPath}`,
'config',
false,
{ credPath },
ErrorCode.AUTH_FAILED,
),
);
}
logger.info('Vertex AI credentials OK');
return ok(undefined);
}
// 4. Check that at least one credential is present
// 3. Check that at least one credential is present
if (!process.env.ANTHROPIC_API_KEY && !process.env.CLAUDE_CODE_OAUTH_TOKEN && !process.env.ANTHROPIC_AUTH_TOKEN) {
return err(
new PentestError(
'No API credentials found. Set ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN in .env (or use CLAUDE_CODE_USE_BEDROCK=1 for AWS Bedrock, or CLAUDE_CODE_USE_VERTEX=1 for Google Vertex AI)',
'No API credentials found. Set ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN in .env (or use CLAUDE_CODE_USE_BEDROCK=1 for AWS Bedrock)',
'config',
false,
{},
@@ -469,7 +418,7 @@ async function validateCredentials(
);
}
// 5. Validate via a minimal pi session
// 4. Validate via a minimal pi session
const authType = process.env.CLAUDE_CODE_OAUTH_TOKEN ? 'OAuth token' : 'API key';
logger.info(`Validating ${authType} via pi...`);
const probe = await probeCredentialsWithPi(authType);
@@ -606,7 +555,7 @@ async function validateTargetUrl(targetUrl: string, logger: ActivityLogger): Pro
* 1. Repository path exists and contains .git
* 2. Config file parses and validates (if configPath provided)
* 3. code_path rules match at least one entry in the repo (skipped without config)
* 4. Credentials validate (API key, OAuth, Bedrock, or Vertex AI)
* 4. Credentials validate (API key, OAuth, or Bedrock)
* 5. Target URL is reachable from the container
*
* Returns on first failure.
+1 -1
View File
@@ -459,7 +459,7 @@ export async function runReportAgent(input: ActivityInput): Promise<AgentMetrics
* Runs cheap checks before any agent execution:
* 1. Repository path exists with .git
* 2. Config file validates (if provided)
* 3. Credential validation (API key, OAuth, Bedrock, or Vertex AI)
* 3. Credential validation (API key, OAuth, or Bedrock)
* 4. Target URL reachable from the container
*
* NOT using runAgentActivity — preflight doesn't run an agent via the SDK.
+1 -1
View File
@@ -28,7 +28,7 @@ export interface PipelineInput {
sastSarifPath?: string; // Optional path for consumer-supplied findings input
checkpointsEnabled?: boolean; // Enable checkpoint activities (default: false)
skipGitCheck?: boolean; // Skip .git directory validation in preflight (e.g. when .git is removed after clone)
providerConfig?: ProviderConfig; // LLM provider configuration (Bedrock, Vertex, etc.)
providerConfig?: ProviderConfig; // LLM provider configuration (Bedrock, custom base URL, etc.)
vulnClasses?: VulnClass[]; // omitted = all five
exploit?: boolean; // false skips the exploitation phase
}
+1 -4
View File
@@ -95,7 +95,7 @@ export interface DistributedConfig {
* LLM provider configuration for multi-provider support.
*
* Resolved by the pi model/provider layer at execution time. Recognized
* providerType values: 'bedrock', 'vertex', 'custom_base_url', 'anthropic_api'.
* providerType values: 'bedrock', 'custom_base_url', 'anthropic_api'.
* When omitted or 'anthropic_api', falls back to apiKey + ANTHROPIC_API_KEY.
*/
export interface ProviderConfig {
@@ -104,9 +104,6 @@ export interface ProviderConfig {
readonly awsRegion?: string;
readonly awsAccessKeyId?: string;
readonly awsSecretAccessKey?: string;
readonly gcpRegion?: string;
readonly gcpProjectId?: string;
readonly gcpCredentialsPath?: string;
readonly baseUrl?: string;
readonly authToken?: string;
readonly modelOverrides?: Record<string, string>;