feat(adapters): add Google Vertex AI
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
fc06f7f05f
commit
3c09be041f
|
|
@ -69,6 +69,11 @@ const hermesLocalCLIAdapter: CLIAdapterModule = {
|
|||
formatStdoutEvent: printHermesStreamEvent,
|
||||
};
|
||||
|
||||
const googleVertexCLIAdapter: CLIAdapterModule = {
|
||||
type: "google_vertex",
|
||||
formatStdoutEvent: printHermesStreamEvent,
|
||||
};
|
||||
|
||||
const openclawGatewayCLIAdapter: CLIAdapterModule = {
|
||||
type: "openclaw_gateway",
|
||||
formatStdoutEvent: printOpenClawGatewayStreamEvent,
|
||||
|
|
@ -87,6 +92,7 @@ const adaptersByType = new Map<string, CLIAdapterModule>(
|
|||
kimiLocalCLIAdapter,
|
||||
hermesGatewayCLIAdapter,
|
||||
hermesLocalCLIAdapter,
|
||||
googleVertexCLIAdapter,
|
||||
openclawGatewayCLIAdapter,
|
||||
processCLIAdapter,
|
||||
httpCLIAdapter,
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ Invariant: every business record belongs to exactly one company.
|
|||
- `status` enum: `active | paused | idle | running | error | pending_approval | terminated`
|
||||
- `reports_to` uuid fk `agents.id` null
|
||||
- `capabilities` text null
|
||||
- `adapter_type` text; built-ins include `process`, `http`, `claude_local`, `codex_local`, `gemini_local`, `opencode_local`, `pi_local`, `cursor`, `hermes_local`, `hermes_gateway`, and `openclaw_gateway`
|
||||
- `adapter_type` text; built-ins include `process`, `http`, `claude_local`, `codex_local`, `gemini_local`, `google_vertex`, `opencode_local`, `pi_local`, `cursor`, `hermes_local`, `hermes_gateway`, and `openclaw_gateway`
|
||||
- `adapter_config` jsonb not null
|
||||
- `runtime_config` jsonb not null default `{}`; contains Paperclip runtime policy such as heartbeat scheduling and debug settings
|
||||
- `default_environment_id` uuid fk `environments.id` null
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
title: "Google Vertex AI"
|
||||
description: "Run Paperclip agents on Gemini models through Google Cloud Vertex AI"
|
||||
---
|
||||
|
||||
The `google_vertex` adapter runs the local Hermes agent harness with its provider fixed to Google Vertex AI. It uses Gemini models through Vertex's OpenAI-compatible endpoint while retaining Hermes tools, skills, and session continuity.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Hermes Agent 0.21.2 or newer on the execution host
|
||||
- A Google Cloud project with the Vertex AI API enabled and billing active
|
||||
- An identity with the `roles/aiplatform.user` role
|
||||
- Either a service-account JSON file or Google Application Default Credentials (ADC)
|
||||
|
||||
## Configure an agent
|
||||
|
||||
Choose **Google Vertex AI** when creating an agent, then set:
|
||||
|
||||
- **Model**: a Vertex model ID such as `google/gemini-3.8-flash`
|
||||
- **Google Cloud project ID**: optional when embedded in the credential
|
||||
- **Vertex region**: defaults to `global`; Gemini 3 preview models require it
|
||||
- **Service-account JSON path**: optional absolute path on the execution host; leave blank to use ADC
|
||||
|
||||
Vertex uses OAuth2 rather than a static API key. Paperclip stores only the credential-file path and non-secret routing configuration. Hermes mints and refreshes short-lived access tokens at runtime.
|
||||
|
||||
For local development with ADC, configure Google Cloud on the same host that executes the agent:
|
||||
|
||||
```bash
|
||||
gcloud auth application-default login
|
||||
```
|
||||
|
||||
For servers, place the service-account JSON outside the repository, restrict its filesystem permissions, and configure its absolute path in the adapter. Do not paste service-account JSON or access tokens into agent configuration fields.
|
||||
|
|
@ -21,6 +21,7 @@ When a heartbeat fires, Paperclip:
|
|||
| [Claude Code](/adapters/claude-local) | `claude_local` | Runs Claude Code CLI locally, with a native ACP engine when available |
|
||||
| [Codex](/adapters/codex-local) | `codex_local` | Runs OpenAI Codex CLI locally, with a native ACP engine when available |
|
||||
| [Gemini CLI](/adapters/gemini-local) | `gemini_local` | Runs Gemini CLI locally (experimental — adapter package exists, not yet in stable type enum) |
|
||||
| [Google Vertex AI](/adapters/google-vertex) | `google_vertex` | Runs Hermes with Gemini on Vertex AI using OAuth2 service-account credentials or ADC |
|
||||
| [Kimi Code CLI](/adapters/kimi-local) | `kimi_local` | Runs Kimi Code CLI locally through ACP, with explicitly selectable headless `-p` mode |
|
||||
| OpenCode | `opencode_local` | Runs OpenCode CLI locally (multi-provider `provider/model`) |
|
||||
| Cursor | `cursor` | Runs Cursor in background mode |
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ Built-in adapters:
|
|||
- `pi_local`: runs an embedded Pi agent locally
|
||||
- `hermes_local`: starts your local `hermes` CLI through `@paperclipai/hermes-paperclip-adapter`
|
||||
- `hermes_gateway`: calls an already-running Hermes API server through `@paperclipai/hermes-paperclip-adapter/gateway`
|
||||
- `google_vertex`: runs Hermes locally with its provider fixed to Google Vertex AI, using OAuth2 service-account credentials or ADC
|
||||
- `openclaw_gateway`: connects to an OpenClaw gateway endpoint
|
||||
- `process`: generic shell command adapter
|
||||
- `http`: calls an external HTTP endpoint
|
||||
|
|
@ -49,7 +50,7 @@ External plugin adapters (install via the adapter manager or API):
|
|||
|
||||
- `droid_local`: runs your local Factory Droid CLI (`@henkey/droid-paperclip-adapter`)
|
||||
|
||||
For local CLI adapters (`claude_local`, `codex_local`, `opencode_local`, `hermes_local`, `droid_local`), Paperclip assumes the CLI is already installed and authenticated on the host machine. For `hermes_gateway`, Paperclip assumes the Hermes API server is already running, reachable from the Paperclip server, and configured with an API key. The older `@paperclipai/adapter-hermes-gateway` npm package is only a deprecated compatibility shim; the adapter type remains `hermes_gateway`.
|
||||
For local CLI adapters (`claude_local`, `codex_local`, `opencode_local`, `hermes_local`, `google_vertex`, `droid_local`), Paperclip assumes the CLI is already installed and authenticated on the host machine. `google_vertex` uses Hermes with Vertex OAuth2 credentials rather than a static model-provider key. For `hermes_gateway`, Paperclip assumes the Hermes API server is already running, reachable from the Paperclip server, and configured with an API key. The older `@paperclipai/adapter-hermes-gateway` npm package is only a deprecated compatibility shim; the adapter type remains `hermes_gateway`.
|
||||
|
||||
## 3.2 Runtime behavior
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@
|
|||
"adapters/overview",
|
||||
"adapters/claude-local",
|
||||
"adapters/codex-local",
|
||||
"adapters/google-vertex",
|
||||
"adapters/process",
|
||||
"adapters/http",
|
||||
"adapters/external-adapters",
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ export const LEGACY_SESSIONED_ADAPTER_TYPES = new Set([
|
|||
"cursor",
|
||||
"gemini_local",
|
||||
"hermes_local",
|
||||
"google_vertex",
|
||||
"kimi_local",
|
||||
"opencode_local",
|
||||
"pi_local",
|
||||
|
|
@ -94,6 +95,11 @@ export const ADAPTER_SESSION_MANAGEMENT: Record<string, AdapterSessionManagement
|
|||
nativeContextManagement: "confirmed",
|
||||
defaultSessionCompaction: ADAPTER_MANAGED_SESSION_POLICY,
|
||||
},
|
||||
google_vertex: {
|
||||
supportsSessionResume: true,
|
||||
nativeContextManagement: "confirmed",
|
||||
defaultSessionCompaction: ADAPTER_MANAGED_SESSION_POLICY,
|
||||
},
|
||||
};
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ A [Paperclip](https://paperclip.ing) adapter package that lets you run [Hermes A
|
|||
|
||||
Hermes Agent is a full-featured AI agent by [Nous Research](https://nousresearch.com) with 30+ native tools, persistent memory, session persistence, 80+ skills, MCP support, and multi-provider model access.
|
||||
|
||||
This package owns both built-in Hermes adapter types:
|
||||
This package owns three built-in Hermes adapter types:
|
||||
|
||||
- `hermes_local` runs the local Hermes CLI as a child process. The package root exports remain compatible with the original local adapter.
|
||||
- `hermes_gateway` calls an already-running Hermes API server over HTTP/SSE. Gateway entrypoints live under the `./gateway` export namespace.
|
||||
- `google_vertex` runs the local Hermes CLI with the provider fixed to Google Vertex AI. Vertex entrypoints live under the `./vertex` export namespace.
|
||||
|
||||
Choose `hermes_local` when Paperclip and Hermes run on the same trusted host
|
||||
and Paperclip should start `hermes chat` for each heartbeat. Choose
|
||||
|
|
|
|||
|
|
@ -32,7 +32,10 @@
|
|||
"./gateway/server": "./src/gateway/server/index.ts",
|
||||
"./gateway/ui": "./src/gateway/ui/index.ts",
|
||||
"./gateway/cli": "./src/gateway/cli/index.ts",
|
||||
"./gateway/ui-parser": "./gateway-ui-parser.cjs"
|
||||
"./gateway/ui-parser": "./gateway-ui-parser.cjs",
|
||||
"./vertex": "./src/vertex/index.ts",
|
||||
"./vertex/server": "./src/vertex/server/index.ts",
|
||||
"./vertex/ui": "./src/vertex/ui/index.ts"
|
||||
},
|
||||
"paperclip": {
|
||||
"adapterUiParser": "1.0.0"
|
||||
|
|
@ -73,7 +76,19 @@
|
|||
"types": "./dist/gateway/cli/index.d.ts",
|
||||
"import": "./dist/gateway/cli/index.js"
|
||||
},
|
||||
"./gateway/ui-parser": "./gateway-ui-parser.cjs"
|
||||
"./gateway/ui-parser": "./gateway-ui-parser.cjs",
|
||||
"./vertex": {
|
||||
"types": "./dist/vertex/index.d.ts",
|
||||
"import": "./dist/vertex/index.js"
|
||||
},
|
||||
"./vertex/server": {
|
||||
"types": "./dist/vertex/server/index.d.ts",
|
||||
"import": "./dist/vertex/server/index.js"
|
||||
},
|
||||
"./vertex/ui": {
|
||||
"types": "./dist/vertex/ui/index.d.ts",
|
||||
"import": "./dist/vertex/ui/index.js"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { expect, test } from "vitest";
|
|||
import {
|
||||
createHermesGatewayServerAdapter,
|
||||
createHermesLocalServerAdapter,
|
||||
createGoogleVertexServerAdapter,
|
||||
createServerAdapter,
|
||||
hermesGatewayType,
|
||||
} from "./index.js";
|
||||
|
|
@ -42,6 +43,21 @@ test("root package export keeps explicit local and gateway adapter factories", (
|
|||
expect(gatewayAdapter.supportsInstructionsBundle).toBe(false);
|
||||
});
|
||||
|
||||
test("root package export exposes the Google Vertex AI adapter factory", () => {
|
||||
const adapter = createGoogleVertexServerAdapter();
|
||||
|
||||
expect(adapter.type).toBe("google_vertex");
|
||||
expect(adapter.models?.[0]).toEqual({
|
||||
id: "google/gemini-3.8-flash",
|
||||
label: "Gemini 3.8 Flash",
|
||||
});
|
||||
expect(adapter.supportsLocalAgentJwt).toBe(true);
|
||||
expect(adapter.supportsInstructionsBundle).toBe(true);
|
||||
expect(typeof adapter.execute).toBe("function");
|
||||
expect(typeof adapter.testEnvironment).toBe("function");
|
||||
expect(typeof adapter.getConfigSchema).toBe("function");
|
||||
});
|
||||
|
||||
test("gateway subpath export exposes the Hermes Gateway adapter entrypoint", () => {
|
||||
const adapter = createGatewayServerAdapterFromSubpath();
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,13 @@ export {
|
|||
models as hermesGatewayModels,
|
||||
type as hermesGatewayType,
|
||||
} from "./gateway/index.js";
|
||||
export {
|
||||
createServerAdapter as createGoogleVertexServerAdapter,
|
||||
agentConfigurationDoc as googleVertexAgentConfigurationDoc,
|
||||
label as googleVertexLabel,
|
||||
models as googleVertexModels,
|
||||
type as googleVertexType,
|
||||
} from "./vertex/index.js";
|
||||
|
||||
/**
|
||||
* Models available through Hermes Agent.
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ export const VALID_PROVIDERS = [
|
|||
"minimax",
|
||||
"minimax-cn",
|
||||
"kilocode",
|
||||
"vertex",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
import type { ServerAdapterModule } from "@paperclipai/adapter-utils";
|
||||
|
||||
import { resolveHermesCommand } from "../server/execute.js";
|
||||
import { listHermesSkills, syncHermesSkills } from "../server/skills.js";
|
||||
import { sessionCodec } from "../server/index.js";
|
||||
import {
|
||||
executeGoogleVertex,
|
||||
getGoogleVertexConfigSchema,
|
||||
testGoogleVertexEnvironment,
|
||||
} from "./server/index.js";
|
||||
import {
|
||||
GOOGLE_VERTEX_ADAPTER_LABEL,
|
||||
GOOGLE_VERTEX_ADAPTER_TYPE,
|
||||
GOOGLE_VERTEX_MODELS,
|
||||
} from "./shared/constants.js";
|
||||
|
||||
export const type = GOOGLE_VERTEX_ADAPTER_TYPE;
|
||||
export const label = GOOGLE_VERTEX_ADAPTER_LABEL;
|
||||
export const models = [...GOOGLE_VERTEX_MODELS];
|
||||
|
||||
export const agentConfigurationDoc = `# Google Vertex AI Configuration
|
||||
|
||||
This adapter runs Hermes Agent with the Google Vertex AI provider fixed for every heartbeat.
|
||||
It uses Gemini models through Vertex's OpenAI-compatible endpoint.
|
||||
|
||||
## Authentication
|
||||
|
||||
Vertex uses OAuth2, not a static API key. Configure one of:
|
||||
|
||||
- \`credentialsPath\`: absolute path to a service-account JSON file on the execution host.
|
||||
- Application Default Credentials (ADC): leave \`credentialsPath\` blank and configure ADC on the host.
|
||||
|
||||
Set \`projectId\` when it cannot be inferred from the credential. The default region is \`global\`, which is required by Gemini 3 preview models. Hermes mints and refreshes short-lived OAuth2 tokens at runtime.
|
||||
`;
|
||||
|
||||
export function createServerAdapter(): ServerAdapterModule {
|
||||
return {
|
||||
type,
|
||||
execute: executeGoogleVertex,
|
||||
testEnvironment: testGoogleVertexEnvironment,
|
||||
sessionCodec,
|
||||
sessionManagement: {
|
||||
supportsSessionResume: true,
|
||||
nativeContextManagement: "confirmed",
|
||||
defaultSessionCompaction: {
|
||||
enabled: true,
|
||||
maxSessionRuns: 0,
|
||||
maxRawInputTokens: 0,
|
||||
maxSessionAgeHours: 0,
|
||||
},
|
||||
},
|
||||
listSkills: listHermesSkills,
|
||||
syncSkills: syncHermesSkills,
|
||||
models,
|
||||
supportsLocalAgentJwt: true,
|
||||
supportsInstructionsBundle: true,
|
||||
instructionsPathKey: "instructionsFilePath",
|
||||
requiresMaterializedRuntimeSkills: false,
|
||||
getRuntimeCommandSpec: (config) => {
|
||||
const command = resolveHermesCommand(config);
|
||||
return { command, detectCommand: command, installCommand: null };
|
||||
},
|
||||
agentConfigurationDoc,
|
||||
getConfigSchema: getGoogleVertexConfigSchema,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import type { AdapterConfigSchema } from "@paperclipai/adapter-utils";
|
||||
|
||||
import { DEFAULT_GRACE_SEC, DEFAULT_TIMEOUT_SEC } from "../../shared/constants.js";
|
||||
import { DEFAULT_GOOGLE_VERTEX_REGION } from "../shared/constants.js";
|
||||
|
||||
export function getGoogleVertexConfigSchema(): AdapterConfigSchema {
|
||||
return {
|
||||
fields: [
|
||||
{
|
||||
key: "projectId",
|
||||
label: "Google Cloud project ID",
|
||||
type: "text",
|
||||
hint: "Optional when the credential contains a project ID; otherwise required.",
|
||||
},
|
||||
{
|
||||
key: "region",
|
||||
label: "Vertex region",
|
||||
type: "text",
|
||||
default: DEFAULT_GOOGLE_VERTEX_REGION,
|
||||
hint: "Use global for Gemini 3 preview models; pin a region only when your deployment requires it.",
|
||||
},
|
||||
{
|
||||
key: "credentialsPath",
|
||||
label: "Service-account JSON path",
|
||||
type: "text",
|
||||
hint: "Optional absolute path on the execution host. Leave blank to use Application Default Credentials.",
|
||||
meta: { path: true },
|
||||
},
|
||||
{
|
||||
key: "timeoutSec",
|
||||
label: "Timeout seconds",
|
||||
type: "number",
|
||||
default: DEFAULT_TIMEOUT_SEC,
|
||||
},
|
||||
{
|
||||
key: "graceSec",
|
||||
label: "Grace seconds",
|
||||
type: "number",
|
||||
default: DEFAULT_GRACE_SEC,
|
||||
},
|
||||
{
|
||||
key: "maxTurnsPerRun",
|
||||
label: "Max turns per run",
|
||||
type: "number",
|
||||
hint: "Optional Hermes tool-calling iteration limit.",
|
||||
},
|
||||
{
|
||||
key: "persistSession",
|
||||
label: "Persist session",
|
||||
type: "toggle",
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
key: "toolsets",
|
||||
label: "Toolsets",
|
||||
type: "text",
|
||||
hint: "Optional comma-separated Hermes toolsets, such as terminal,file,web.",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
DEFAULT_GOOGLE_VERTEX_MODEL,
|
||||
DEFAULT_GOOGLE_VERTEX_REGION,
|
||||
GOOGLE_VERTEX_PROVIDER,
|
||||
} from "../shared/constants.js";
|
||||
import { buildGoogleVertexRuntimeConfig } from "./config.js";
|
||||
|
||||
describe("buildGoogleVertexRuntimeConfig", () => {
|
||||
it("fixes the provider and supplies safe Vertex defaults", () => {
|
||||
expect(buildGoogleVertexRuntimeConfig({ provider: "openrouter" })).toMatchObject({
|
||||
provider: GOOGLE_VERTEX_PROVIDER,
|
||||
model: DEFAULT_GOOGLE_VERTEX_MODEL,
|
||||
env: { VERTEX_REGION: DEFAULT_GOOGLE_VERTEX_REGION },
|
||||
});
|
||||
});
|
||||
|
||||
it("maps routing fields and the service-account pointer into Hermes env", () => {
|
||||
const config = buildGoogleVertexRuntimeConfig({
|
||||
model: " google/gemini-3.1-pro-preview ",
|
||||
projectId: " project-123 ",
|
||||
region: " us-central1 ",
|
||||
credentialsPath: " /secure/vertex.json ",
|
||||
env: { EXISTING: "value" },
|
||||
});
|
||||
|
||||
expect(config).toMatchObject({
|
||||
provider: "vertex",
|
||||
model: "google/gemini-3.1-pro-preview",
|
||||
env: {
|
||||
EXISTING: "value",
|
||||
VERTEX_PROJECT_ID: "project-123",
|
||||
VERTEX_REGION: "us-central1",
|
||||
VERTEX_CREDENTIALS_PATH: "/secure/vertex.json",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import type { AdapterExecutionContext, AdapterEnvironmentTestContext } from "@paperclipai/adapter-utils";
|
||||
|
||||
import {
|
||||
DEFAULT_GOOGLE_VERTEX_MODEL,
|
||||
DEFAULT_GOOGLE_VERTEX_REGION,
|
||||
GOOGLE_VERTEX_PROVIDER,
|
||||
} from "../shared/constants.js";
|
||||
|
||||
function nonEmptyString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
export function buildGoogleVertexRuntimeConfig(
|
||||
config: Record<string, unknown>,
|
||||
): Record<string, unknown> {
|
||||
const env =
|
||||
config.env && typeof config.env === "object" && !Array.isArray(config.env)
|
||||
? { ...(config.env as Record<string, unknown>) }
|
||||
: {};
|
||||
const projectId = nonEmptyString(config.projectId);
|
||||
const region = nonEmptyString(config.region) ?? DEFAULT_GOOGLE_VERTEX_REGION;
|
||||
const credentialsPath = nonEmptyString(config.credentialsPath);
|
||||
|
||||
if (projectId) env.VERTEX_PROJECT_ID = projectId;
|
||||
if (region) env.VERTEX_REGION = region;
|
||||
if (credentialsPath) env.VERTEX_CREDENTIALS_PATH = credentialsPath;
|
||||
|
||||
return {
|
||||
...config,
|
||||
env,
|
||||
provider: GOOGLE_VERTEX_PROVIDER,
|
||||
model: nonEmptyString(config.model) ?? DEFAULT_GOOGLE_VERTEX_MODEL,
|
||||
};
|
||||
}
|
||||
|
||||
export function withGoogleVertexExecutionConfig(
|
||||
ctx: AdapterExecutionContext,
|
||||
): AdapterExecutionContext {
|
||||
const config = buildGoogleVertexRuntimeConfig(ctx.config ?? {});
|
||||
return {
|
||||
...ctx,
|
||||
config,
|
||||
agent: { ...ctx.agent, adapterConfig: config },
|
||||
};
|
||||
}
|
||||
|
||||
export function withGoogleVertexTestConfig(
|
||||
ctx: AdapterEnvironmentTestContext,
|
||||
): AdapterEnvironmentTestContext {
|
||||
return { ...ctx, config: buildGoogleVertexRuntimeConfig(ctx.config ?? {}) };
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
import type {
|
||||
AdapterEnvironmentCheck,
|
||||
AdapterEnvironmentTestContext,
|
||||
AdapterEnvironmentTestResult,
|
||||
AdapterExecutionContext,
|
||||
} from "@paperclipai/adapter-utils";
|
||||
import { access } from "node:fs/promises";
|
||||
|
||||
import { execute as executeHermes } from "../../server/execute.js";
|
||||
import { testEnvironment as testHermesEnvironment } from "../../server/test.js";
|
||||
import { GOOGLE_VERTEX_ADAPTER_TYPE } from "../shared/constants.js";
|
||||
import {
|
||||
buildGoogleVertexRuntimeConfig,
|
||||
withGoogleVertexExecutionConfig,
|
||||
withGoogleVertexTestConfig,
|
||||
} from "./config.js";
|
||||
export { getGoogleVertexConfigSchema } from "./config-schema.js";
|
||||
export { buildGoogleVertexRuntimeConfig } from "./config.js";
|
||||
|
||||
export async function executeGoogleVertex(ctx: AdapterExecutionContext) {
|
||||
return executeHermes(withGoogleVertexExecutionConfig(ctx));
|
||||
}
|
||||
|
||||
async function vertexCredentialCheck(
|
||||
config: Record<string, unknown>,
|
||||
): Promise<AdapterEnvironmentCheck> {
|
||||
const env = (config.env ?? {}) as Record<string, unknown>;
|
||||
const credentialPath =
|
||||
typeof env.VERTEX_CREDENTIALS_PATH === "string" && env.VERTEX_CREDENTIALS_PATH.length > 0
|
||||
? env.VERTEX_CREDENTIALS_PATH
|
||||
: typeof env.GOOGLE_APPLICATION_CREDENTIALS === "string" && env.GOOGLE_APPLICATION_CREDENTIALS.length > 0
|
||||
? env.GOOGLE_APPLICATION_CREDENTIALS
|
||||
: null;
|
||||
if (credentialPath) {
|
||||
try {
|
||||
await access(credentialPath);
|
||||
} catch {
|
||||
return {
|
||||
level: "error",
|
||||
message: "Vertex service-account credential file is not readable",
|
||||
hint: "Check credentialsPath on the selected execution host, or leave it blank to use Application Default Credentials.",
|
||||
code: "google_vertex_service_account_unreadable",
|
||||
};
|
||||
}
|
||||
return {
|
||||
level: "info",
|
||||
message: "Vertex service-account credential path is configured",
|
||||
hint: "Hermes will mint and refresh OAuth2 access tokens at runtime; tokens are never stored in Paperclip config.",
|
||||
code: "google_vertex_service_account_configured",
|
||||
};
|
||||
}
|
||||
return {
|
||||
level: "info",
|
||||
message: "Vertex will use Google Application Default Credentials",
|
||||
hint: "Ensure ADC is available on the selected execution host and can access the configured Google Cloud project.",
|
||||
code: "google_vertex_adc",
|
||||
};
|
||||
}
|
||||
|
||||
export async function testGoogleVertexEnvironment(
|
||||
ctx: AdapterEnvironmentTestContext,
|
||||
): Promise<AdapterEnvironmentTestResult> {
|
||||
const vertexCtx = withGoogleVertexTestConfig(ctx);
|
||||
const result = await testHermesEnvironment(vertexCtx);
|
||||
const checks = result.checks.filter((check) => check.code !== "hermes_no_api_keys");
|
||||
checks.push(await vertexCredentialCheck(buildGoogleVertexRuntimeConfig(ctx.config ?? {})));
|
||||
const hasErrors = checks.some((check) => check.level === "error");
|
||||
const hasWarnings = checks.some((check) => check.level === "warn");
|
||||
return {
|
||||
...result,
|
||||
adapterType: GOOGLE_VERTEX_ADAPTER_TYPE,
|
||||
status: hasErrors ? "fail" : hasWarnings ? "warn" : "pass",
|
||||
checks,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
export const GOOGLE_VERTEX_ADAPTER_TYPE = "google_vertex";
|
||||
export const GOOGLE_VERTEX_ADAPTER_LABEL = "Google Vertex AI";
|
||||
export const GOOGLE_VERTEX_PROVIDER = "vertex";
|
||||
export const DEFAULT_GOOGLE_VERTEX_MODEL = "google/gemini-3.8-flash";
|
||||
export const DEFAULT_GOOGLE_VERTEX_REGION = "global";
|
||||
|
||||
/** Vertex's OpenAI-compatible endpoint has no model-list route. */
|
||||
export const GOOGLE_VERTEX_MODELS = [
|
||||
{ id: "google/gemini-3.8-flash", label: "Gemini 3.8 Flash" },
|
||||
{ id: "google/gemini-3.7-flash", label: "Gemini 3.7 Flash" },
|
||||
{ id: "google/gemini-3.1-pro-preview", label: "Gemini 3.1 Pro Preview" },
|
||||
{ id: "google/gemini-3-pro-preview", label: "Gemini 3 Pro Preview" },
|
||||
{ id: "google/gemini-3.6-flash", label: "Gemini 3.6 Flash" },
|
||||
{ id: "google/gemini-3.5-flash", label: "Gemini 3.5 Flash" },
|
||||
{ id: "google/gemini-3.5-flash-lite", label: "Gemini 3.5 Flash Lite" },
|
||||
{ id: "google/gemini-3-flash-preview", label: "Gemini 3 Flash Preview" },
|
||||
{ id: "google/gemini-3.1-flash-lite-preview", label: "Gemini 3.1 Flash Lite Preview" },
|
||||
{ id: "google/gemini-3.1-flash-lite", label: "Gemini 3.1 Flash Lite" },
|
||||
] as const;
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import type { CreateConfigValues } from "@paperclipai/adapter-utils";
|
||||
|
||||
import { DEFAULT_GOOGLE_VERTEX_MODEL } from "../shared/constants.js";
|
||||
import { buildGoogleVertexConfig } from "./build-config.js";
|
||||
|
||||
function values(overrides: Partial<CreateConfigValues> = {}): CreateConfigValues {
|
||||
return {
|
||||
adapterType: "google_vertex",
|
||||
cwd: "",
|
||||
promptTemplate: "",
|
||||
model: "",
|
||||
thinkingEffort: "",
|
||||
chrome: false,
|
||||
dangerouslySkipPermissions: false,
|
||||
search: false,
|
||||
fastMode: false,
|
||||
dangerouslyBypassSandbox: false,
|
||||
command: "",
|
||||
args: "",
|
||||
extraArgs: "",
|
||||
envVars: "",
|
||||
envBindings: {},
|
||||
url: "",
|
||||
bootstrapPrompt: "",
|
||||
payloadTemplateJson: "",
|
||||
maxTurnsPerRun: 0,
|
||||
heartbeatEnabled: true,
|
||||
intervalSec: 3600,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("buildGoogleVertexConfig", () => {
|
||||
it("creates a Vertex-only config with the default model and region", () => {
|
||||
expect(buildGoogleVertexConfig(values())).toMatchObject({
|
||||
provider: "vertex",
|
||||
model: DEFAULT_GOOGLE_VERTEX_MODEL,
|
||||
region: "global",
|
||||
persistSession: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps schema routing fields while preventing a provider override", () => {
|
||||
expect(
|
||||
buildGoogleVertexConfig(
|
||||
values({
|
||||
model: "google/gemini-3.1-pro-preview",
|
||||
adapterSchemaValues: {
|
||||
provider: "openrouter",
|
||||
projectId: "project-123",
|
||||
region: "us-central1",
|
||||
credentialsPath: "/secure/vertex.json",
|
||||
},
|
||||
}),
|
||||
),
|
||||
).toMatchObject({
|
||||
provider: "vertex",
|
||||
model: "google/gemini-3.1-pro-preview",
|
||||
projectId: "project-123",
|
||||
region: "us-central1",
|
||||
credentialsPath: "/secure/vertex.json",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import type { CreateConfigValues } from "@paperclipai/adapter-utils";
|
||||
|
||||
import { buildHermesConfig } from "../../ui/build-config.js";
|
||||
import {
|
||||
DEFAULT_GOOGLE_VERTEX_MODEL,
|
||||
DEFAULT_GOOGLE_VERTEX_REGION,
|
||||
GOOGLE_VERTEX_PROVIDER,
|
||||
} from "../shared/constants.js";
|
||||
|
||||
export function buildGoogleVertexConfig(values: CreateConfigValues): Record<string, unknown> {
|
||||
return {
|
||||
...buildHermesConfig({
|
||||
...values,
|
||||
model: values.model.trim() || DEFAULT_GOOGLE_VERTEX_MODEL,
|
||||
}),
|
||||
...(values.adapterSchemaValues ?? {}),
|
||||
provider: GOOGLE_VERTEX_PROVIDER,
|
||||
region:
|
||||
typeof values.adapterSchemaValues?.region === "string" && values.adapterSchemaValues.region.trim()
|
||||
? values.adapterSchemaValues.region.trim()
|
||||
: DEFAULT_GOOGLE_VERTEX_REGION,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export { buildGoogleVertexConfig } from "./build-config.js";
|
||||
export { parseHermesStdoutLine as parseGoogleVertexStdoutLine } from "../../ui/parse-stdout.js";
|
||||
|
|
@ -32,6 +32,7 @@ export const AGENT_ADAPTER_TYPES = [
|
|||
"paperclip_runner",
|
||||
"cursor_cloud",
|
||||
"gemini_local",
|
||||
"google_vertex",
|
||||
"grok_local",
|
||||
"hermes_gateway",
|
||||
"hermes_local",
|
||||
|
|
|
|||
|
|
@ -189,6 +189,20 @@ describe("server adapter registry", () => {
|
|||
expect(requireServerAdapter("hermes_gateway")).toBe(builtInGateway);
|
||||
});
|
||||
|
||||
it("ships Google Vertex AI as a Hermes-backed built-in adapter", () => {
|
||||
const adapter = findServerAdapter("google_vertex");
|
||||
|
||||
expect(adapter).not.toBeNull();
|
||||
expect(adapter?.supportsLocalAgentJwt).toBe(true);
|
||||
expect(adapter?.supportsInstructionsBundle).toBe(true);
|
||||
expect(adapter?.requiresMaterializedRuntimeSkills).toBe(false);
|
||||
expect(adapter?.getConfigSchema).toBeTypeOf("function");
|
||||
expect(adapter?.models?.[0]).toEqual({
|
||||
id: "google/gemini-3.8-flash",
|
||||
label: "Gemini 3.8 Flash",
|
||||
});
|
||||
});
|
||||
|
||||
it("exposes capability flags from registered adapters", () => {
|
||||
const adapterWithCaps: ServerAdapterModule = {
|
||||
type: "external_test",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export const BUILTIN_ADAPTER_TYPES = new Set([
|
|||
"cursor_cloud",
|
||||
"cursor",
|
||||
"gemini_local",
|
||||
"google_vertex",
|
||||
"grok_local",
|
||||
"hermes_gateway",
|
||||
"hermes_local",
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ describe("built-in runtime connection tool delivery", () => {
|
|||
["grok_local", "environment"],
|
||||
["hermes_gateway", "invocation_context"],
|
||||
["hermes_local", "environment"],
|
||||
["google_vertex", "environment"],
|
||||
["kimi_local", "environment"],
|
||||
["openclaw_gateway", "invocation_context"],
|
||||
["opencode_local", "environment"],
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ import {
|
|||
import {
|
||||
createHermesGatewayServerAdapter,
|
||||
createHermesLocalServerAdapter,
|
||||
createGoogleVertexServerAdapter,
|
||||
} from "@paperclipai/hermes-paperclip-adapter";
|
||||
import {
|
||||
execute as openCodeExecute,
|
||||
|
|
@ -788,6 +789,11 @@ const hermesLocalAdapter: ServerAdapterModule = {
|
|||
runtimeToolDelivery: "environment",
|
||||
};
|
||||
|
||||
const googleVertexAdapter: ServerAdapterModule = {
|
||||
...createGoogleVertexServerAdapter(),
|
||||
runtimeToolDelivery: "environment",
|
||||
};
|
||||
|
||||
const openclawGatewayAdapter: ServerAdapterModule = {
|
||||
type: "openclaw_gateway",
|
||||
runtimeToolDelivery: "invocation_context",
|
||||
|
|
@ -865,6 +871,7 @@ function registerBuiltInAdapters() {
|
|||
kimiLocalAdapter,
|
||||
hermesGatewayAdapter,
|
||||
hermesLocalAdapter,
|
||||
googleVertexAdapter,
|
||||
openclawGatewayAdapter,
|
||||
processAdapter,
|
||||
httpAdapter,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ const SESSIONED_LOCAL_ADAPTERS = new Set([
|
|||
"cursor",
|
||||
"gemini_local",
|
||||
"hermes_local",
|
||||
"google_vertex",
|
||||
"kimi_local",
|
||||
"opencode_local",
|
||||
"pi_local",
|
||||
|
|
|
|||
|
|
@ -482,6 +482,7 @@ export function agentRoutes(
|
|||
codex_local: "instructionsFilePath",
|
||||
droid_local: "instructionsFilePath",
|
||||
gemini_local: "instructionsFilePath",
|
||||
google_vertex: "instructionsFilePath",
|
||||
kimi_local: "instructionsFilePath",
|
||||
opencode_local: "instructionsFilePath",
|
||||
cursor: "instructionsFilePath",
|
||||
|
|
|
|||
|
|
@ -806,6 +806,7 @@ const GIT_SENSITIVE_LOCAL_ADAPTER_TYPES = new Set([
|
|||
"gemini_local",
|
||||
"grok_local",
|
||||
"hermes_local",
|
||||
"google_vertex",
|
||||
"kimi_local",
|
||||
"opencode_local",
|
||||
"pi_local",
|
||||
|
|
@ -1195,6 +1196,7 @@ const SESSIONED_LOCAL_ADAPTERS = new Set([
|
|||
"cursor",
|
||||
"gemini_local",
|
||||
"hermes_local",
|
||||
"google_vertex",
|
||||
"kimi_local",
|
||||
"opencode_local",
|
||||
"pi_local",
|
||||
|
|
@ -8773,12 +8775,12 @@ export function resolveSkillTestRunCompletionForHeartbeatOutcome(
|
|||
return null;
|
||||
}
|
||||
|
||||
const HERMES_ADAPTER_TYPE = "hermes_local";
|
||||
const HERMES_ADAPTER_TYPES = new Set(["hermes_local", "google_vertex"]);
|
||||
const HERMES_SESSION_ID_REGEX =
|
||||
/^(?:\d{8}_\d{6}_[A-Za-z0-9_-]{4,}|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/;
|
||||
|
||||
function requiresCanonicalSessionIds(adapterType: string | null | undefined) {
|
||||
return adapterType === HERMES_ADAPTER_TYPE;
|
||||
return typeof adapterType === "string" && HERMES_ADAPTER_TYPES.has(adapterType);
|
||||
}
|
||||
|
||||
function isCanonicalSessionIdForAdapter(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ describe("adapter display registry", () => {
|
|||
expect(getAdapterLabel("acpx_local")).toBe("ACPX (retired)");
|
||||
expect(getAdapterLabel("cursor")).toBe("Cursor");
|
||||
expect(getAdapterLabel("gemini_local")).toBe("Gemini CLI");
|
||||
expect(getAdapterLabel("google_vertex")).toBe("Google Vertex AI");
|
||||
expect(getAdapterLabel("grok_local")).toBe("Grok Build");
|
||||
expect(getAdapterLabel("kimi_local")).toBe("Kimi Code");
|
||||
expect(getAdapterLabel("hermes_local")).toBe("Hermes");
|
||||
|
|
@ -22,6 +23,7 @@ describe("adapter display registry", () => {
|
|||
acpx_local: "ACPX (retired)",
|
||||
cursor: "Cursor",
|
||||
gemini_local: "Gemini CLI",
|
||||
google_vertex: "Google Vertex AI",
|
||||
grok_local: "Grok Build",
|
||||
kimi_local: "Kimi Code",
|
||||
hermes_local: "Hermes",
|
||||
|
|
|
|||
|
|
@ -91,6 +91,11 @@ const adapterDisplayMap: Record<string, AdapterDisplayInfo> = {
|
|||
description: "Gemini CLI harness",
|
||||
icon: Gem,
|
||||
},
|
||||
google_vertex: {
|
||||
label: "Google Vertex AI",
|
||||
description: "Gemini on Google Cloud via Hermes",
|
||||
icon: Gem,
|
||||
},
|
||||
grok_local: {
|
||||
label: "Grok Build",
|
||||
description: "Grok Build harness",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
import type { UIAdapterModule } from "../types";
|
||||
import {
|
||||
buildGoogleVertexConfig,
|
||||
parseGoogleVertexStdoutLine,
|
||||
} from "@paperclipai/hermes-paperclip-adapter/vertex/ui";
|
||||
import { SchemaConfigFields } from "../schema-config-fields";
|
||||
|
||||
export const googleVertexUIAdapter: UIAdapterModule = {
|
||||
type: "google_vertex",
|
||||
label: "Google Vertex AI",
|
||||
parseStdoutLine: parseGoogleVertexStdoutLine,
|
||||
ConfigFields: SchemaConfigFields,
|
||||
buildAdapterConfig: buildGoogleVertexConfig,
|
||||
};
|
||||
|
|
@ -77,4 +77,11 @@ describe("ui adapter registry", () => {
|
|||
expect(getUIAdapter(type)).toBe(builtin);
|
||||
}
|
||||
});
|
||||
|
||||
it("registers the Google Vertex AI UI adapter", () => {
|
||||
const adapter = getUIAdapter("google_vertex");
|
||||
|
||||
expect(adapter.label).toBe("Google Vertex AI");
|
||||
expect(adapter.ConfigFields).toBe(SchemaConfigFields);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { paperclipRunnerUIAdapter } from "./paperclip-runner";
|
|||
import { cursorCloudUIAdapter } from "./cursor-cloud";
|
||||
import { cursorLocalUIAdapter } from "./cursor";
|
||||
import { geminiLocalUIAdapter } from "./gemini-local";
|
||||
import { googleVertexUIAdapter } from "./google-vertex";
|
||||
import { grokLocalUIAdapter } from "./grok-local";
|
||||
import { kimiLocalUIAdapter } from "./kimi-local";
|
||||
import { hermesGatewayUIAdapter } from "./hermes-gateway";
|
||||
|
|
@ -59,6 +60,7 @@ function registerBuiltInUIAdapters() {
|
|||
paperclipRunnerUIAdapter,
|
||||
cursorCloudUIAdapter,
|
||||
geminiLocalUIAdapter,
|
||||
googleVertexUIAdapter,
|
||||
grokLocalUIAdapter,
|
||||
kimiLocalUIAdapter,
|
||||
hermesGatewayUIAdapter,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ const KNOWN_DEFAULTS: Record<string, AdapterCapabilities> = {
|
|||
paperclip_runner: { supportsInstructionsBundle: true, supportsSkills: true, supportsLocalAgentJwt: false, requiresMaterializedRuntimeSkills: false, supportsAcp: false },
|
||||
cursor: { supportsInstructionsBundle: true, supportsSkills: true, supportsLocalAgentJwt: true, requiresMaterializedRuntimeSkills: true, supportsAcp: false },
|
||||
gemini_local: { supportsInstructionsBundle: true, supportsSkills: true, supportsLocalAgentJwt: true, requiresMaterializedRuntimeSkills: true, supportsAcp: true },
|
||||
google_vertex: { supportsInstructionsBundle: true, supportsSkills: true, supportsLocalAgentJwt: true, requiresMaterializedRuntimeSkills: false, supportsAcp: false },
|
||||
grok_local: { supportsInstructionsBundle: true, supportsSkills: true, supportsLocalAgentJwt: true, requiresMaterializedRuntimeSkills: true, supportsAcp: false, login: { panelMode: "displayed_code", timeoutPolicy: "caller_bounded" } },
|
||||
kimi_local: { supportsInstructionsBundle: true, supportsSkills: true, supportsLocalAgentJwt: true, requiresMaterializedRuntimeSkills: true, supportsAcp: true },
|
||||
opencode_local: { supportsInstructionsBundle: true, supportsSkills: true, supportsLocalAgentJwt: true, requiresMaterializedRuntimeSkills: true, supportsAcp: false },
|
||||
|
|
|
|||
|
|
@ -1068,6 +1068,7 @@ function OnboardingWizardInner({
|
|||
adapterType === "claude_local" ||
|
||||
adapterType === "codex_local" ||
|
||||
adapterType === "gemini_local" ||
|
||||
adapterType === "google_vertex" ||
|
||||
adapterType === "kimi_local" ||
|
||||
adapterType === "opencode_local" ||
|
||||
adapterType === "pi_local" ||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ const brandMarks: Record<string, { src: string; dark?: string }> = {
|
|||
claude_local: { src: "/brands/claude-color.svg" },
|
||||
codex_local: { src: "/brands/codex-color.svg" },
|
||||
gemini_local: { src: "/brands/adapters/gemini-color.svg" },
|
||||
google_vertex: { src: "/brands/adapters/gemini-color.svg" },
|
||||
kimi_local: {
|
||||
src: "/brands/adapters/kimi-color-light.svg",
|
||||
dark: "/brands/adapters/kimi-color.svg",
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ const ENABLED_INVITE_ADAPTERS = new Set([
|
|||
"claude_local",
|
||||
"codex_local",
|
||||
"gemini_local",
|
||||
"google_vertex",
|
||||
"kimi_local",
|
||||
"opencode_local",
|
||||
"pi_local",
|
||||
|
|
|
|||
Loading…
Reference in New Issue