c43ed65c3b
* add Vertex AI routing support * include Vertex smokes in action CI
140 lines
4.9 KiB
TypeScript
140 lines
4.9 KiB
TypeScript
import type { Agent } from "../agents/index.ts";
|
|
import { agents } from "../agents/index.ts";
|
|
import {
|
|
BEDROCK_MODEL_ID_ENV,
|
|
getModelProvider,
|
|
isBedrockAnthropicId,
|
|
isVertexAnthropicId,
|
|
resolveCliModel,
|
|
resolveDisplayAlias,
|
|
VERTEX_MODEL_ID_ENV,
|
|
} from "../models.ts";
|
|
import { log } from "./cli.ts";
|
|
import { VERTEX_SERVICE_ACCOUNT_JSON_ENV } from "./vertex.ts";
|
|
|
|
function hasEnvVar(name: string): boolean {
|
|
const val = process.env[name];
|
|
return typeof val === "string" && val.length > 0;
|
|
}
|
|
|
|
function hasClaudeCodeAuth(): boolean {
|
|
return hasEnvVar("CLAUDE_CODE_OAUTH_TOKEN") || hasEnvVar("ANTHROPIC_API_KEY");
|
|
}
|
|
|
|
function hasBedrockAuth(): boolean {
|
|
return (
|
|
hasEnvVar("AWS_BEARER_TOKEN_BEDROCK") ||
|
|
(hasEnvVar("AWS_ACCESS_KEY_ID") && hasEnvVar("AWS_SECRET_ACCESS_KEY"))
|
|
);
|
|
}
|
|
|
|
function hasVertexAuth(): boolean {
|
|
return hasEnvVar(VERTEX_SERVICE_ACCOUNT_JSON_ENV);
|
|
}
|
|
|
|
/**
|
|
* resolve a single slug to its CLI-ready model string. routing aliases
|
|
* (e.g. `bedrock/byok`) defer to their backing env var instead of the
|
|
* sentinel stored in `resolve`. shared between PULLFROG_MODEL override
|
|
* and repo-config slug resolution so both paths get the same routing
|
|
* semantics — without this helper, `PULLFROG_MODEL=bedrock/byok` would
|
|
* leak the literal sentinel string `"bedrock"` downstream.
|
|
*/
|
|
function resolveSlug(slug: string): string | undefined {
|
|
const alias = resolveDisplayAlias(slug);
|
|
if (alias?.routing === "bedrock") {
|
|
const bedrockId = process.env[BEDROCK_MODEL_ID_ENV]?.trim();
|
|
if (!bedrockId) {
|
|
throw new Error(
|
|
`${BEDROCK_MODEL_ID_ENV} env var is required when the model is set to "${slug}". ` +
|
|
`set it to an AWS Bedrock model ID from the Bedrock console. ` +
|
|
`see https://docs.pullfrog.com/bedrock for setup.`
|
|
);
|
|
}
|
|
return bedrockId;
|
|
}
|
|
if (alias?.routing === "vertex") {
|
|
const vertexId = process.env[VERTEX_MODEL_ID_ENV]?.trim();
|
|
if (!vertexId) {
|
|
throw new Error(
|
|
`${VERTEX_MODEL_ID_ENV} env var is required when the model is set to "${slug}". ` +
|
|
`set it to a Google Vertex AI model ID from Model Garden. ` +
|
|
`see https://docs.pullfrog.com/vertex for setup.`
|
|
);
|
|
}
|
|
return vertexId;
|
|
}
|
|
return resolveCliModel(slug);
|
|
}
|
|
|
|
/**
|
|
* resolve the effective model for this run.
|
|
*
|
|
* priority:
|
|
* 1. PULLFROG_MODEL env var — resolved through the alias registry first,
|
|
* so values like "anthropic/claude-opus" become "anthropic/claude-opus-4-7".
|
|
* raw specifiers (e.g. "anthropic/claude-opus-4-6") pass through unchanged.
|
|
* always wins — bypasses Bedrock routing entirely. to test a different
|
|
* Bedrock model, change `BEDROCK_MODEL_ID`, not `PULLFROG_MODEL`.
|
|
* 2. slug from repo config / payload → alias registry. routing slugs
|
|
* (e.g. `bedrock/byok`) defer to a separate env var (`BEDROCK_MODEL_ID`).
|
|
* 3. undefined — agent will auto-select.
|
|
*/
|
|
export function resolveModel(ctx: { slug?: string | undefined }): string | undefined {
|
|
const envModel = process.env.PULLFROG_MODEL?.trim();
|
|
if (envModel) {
|
|
return resolveSlug(envModel) ?? envModel;
|
|
}
|
|
|
|
if (ctx.slug) {
|
|
const resolved = resolveSlug(ctx.slug);
|
|
if (resolved) {
|
|
return resolved;
|
|
}
|
|
log.warning(`» unknown model slug "${ctx.slug}" — agent will auto-select`);
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
export function resolveAgent(ctx: { model?: string | undefined }): Agent {
|
|
// 1. explicit env var override (escape hatch)
|
|
const envAgent = process.env.PULLFROG_AGENT?.trim();
|
|
if (envAgent) {
|
|
if (envAgent in agents) {
|
|
return agents[envAgent as keyof typeof agents];
|
|
}
|
|
log.warning(`» unknown PULLFROG_AGENT="${envAgent}" — falling through to auto-select`);
|
|
}
|
|
|
|
// 2. Bedrock routing: when BEDROCK_MODEL_ID is the resolved model, route
|
|
// Anthropic IDs through claude-code (which supports Bedrock natively
|
|
// once CLAUDE_CODE_USE_BEDROCK=1) and everything else through opencode's
|
|
// `amazon-bedrock` provider.
|
|
if (ctx.model && hasBedrockAuth() && process.env[BEDROCK_MODEL_ID_ENV]?.trim() === ctx.model) {
|
|
return isBedrockAnthropicId(ctx.model) ? agents.claude : agents.opencode;
|
|
}
|
|
|
|
// 3. Vertex routing: same shape as Bedrock, but Anthropic Vertex IDs are
|
|
// anchored `claude-*` IDs and non-Anthropic models use opencode's
|
|
// `google-vertex` provider.
|
|
if (ctx.model && hasVertexAuth() && process.env[VERTEX_MODEL_ID_ENV]?.trim() === ctx.model) {
|
|
return isVertexAnthropicId(ctx.model) ? agents.claude : agents.opencode;
|
|
}
|
|
|
|
// 4. if model is Anthropic and Claude Code credentials are available, use Claude Code
|
|
if (ctx.model) {
|
|
try {
|
|
const provider = getModelProvider(ctx.model);
|
|
if (provider === "anthropic" && hasClaudeCodeAuth()) {
|
|
return agents.claude;
|
|
}
|
|
} catch {
|
|
// invalid model format — fall through
|
|
}
|
|
}
|
|
|
|
// 5. default: OpenCode (universal, supports all providers)
|
|
return agents.opencode;
|
|
}
|