@gakr-gakr/anthropic-vertex-provider 0.1.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/api.ts +74 -0
- package/autobot.plugin.json +16 -0
- package/index.ts +60 -0
- package/package.json +39 -0
- package/provider-catalog.ts +66 -0
- package/provider-discovery.ts +92 -0
- package/region.ts +145 -0
- package/setup-api.ts +16 -0
- package/stream-runtime.ts +221 -0
- package/tsconfig.json +16 -0
package/api.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { StreamFn } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import type { AnthropicVertexStreamDeps } from "./stream-runtime.js";
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
ANTHROPIC_VERTEX_DEFAULT_MODEL_ID,
|
|
6
|
+
buildAnthropicVertexProvider,
|
|
7
|
+
} from "./provider-catalog.js";
|
|
8
|
+
export {
|
|
9
|
+
hasAnthropicVertexAvailableAuth,
|
|
10
|
+
hasAnthropicVertexCredentials,
|
|
11
|
+
resolveAnthropicVertexClientRegion,
|
|
12
|
+
resolveAnthropicVertexConfigApiKey,
|
|
13
|
+
resolveAnthropicVertexProjectId,
|
|
14
|
+
resolveAnthropicVertexRegion,
|
|
15
|
+
resolveAnthropicVertexRegionFromBaseUrl,
|
|
16
|
+
} from "./region.js";
|
|
17
|
+
import { buildAnthropicVertexProvider } from "./provider-catalog.js";
|
|
18
|
+
import { hasAnthropicVertexAvailableAuth } from "./region.js";
|
|
19
|
+
|
|
20
|
+
export function mergeImplicitAnthropicVertexProvider(params: {
|
|
21
|
+
existing?: ReturnType<typeof buildAnthropicVertexProvider>;
|
|
22
|
+
implicit: ReturnType<typeof buildAnthropicVertexProvider>;
|
|
23
|
+
}) {
|
|
24
|
+
const { existing, implicit } = params;
|
|
25
|
+
if (!existing) {
|
|
26
|
+
return implicit;
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
...implicit,
|
|
30
|
+
...existing,
|
|
31
|
+
models:
|
|
32
|
+
Array.isArray(existing.models) && existing.models.length > 0
|
|
33
|
+
? existing.models
|
|
34
|
+
: implicit.models,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function resolveImplicitAnthropicVertexProvider(params?: { env?: NodeJS.ProcessEnv }) {
|
|
39
|
+
const env = params?.env ?? process.env;
|
|
40
|
+
if (!hasAnthropicVertexAvailableAuth(env)) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return buildAnthropicVertexProvider({ env });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createAnthropicVertexStreamFn(
|
|
48
|
+
projectId: string | undefined,
|
|
49
|
+
region: string,
|
|
50
|
+
baseURL?: string,
|
|
51
|
+
deps?: AnthropicVertexStreamDeps,
|
|
52
|
+
): StreamFn {
|
|
53
|
+
const streamFnPromise = import("./stream-runtime.js").then((runtime) =>
|
|
54
|
+
runtime.createAnthropicVertexStreamFn(projectId, region, baseURL, deps),
|
|
55
|
+
);
|
|
56
|
+
return async (model, context, options) => {
|
|
57
|
+
const streamFn = await streamFnPromise;
|
|
58
|
+
return streamFn(model, context, options);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function createAnthropicVertexStreamFnForModel(
|
|
63
|
+
model: { baseUrl?: string },
|
|
64
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
65
|
+
deps?: AnthropicVertexStreamDeps,
|
|
66
|
+
): StreamFn {
|
|
67
|
+
const streamFnPromise = import("./stream-runtime.js").then((runtime) =>
|
|
68
|
+
runtime.createAnthropicVertexStreamFnForModel(model, env, deps),
|
|
69
|
+
);
|
|
70
|
+
return async (...args) => {
|
|
71
|
+
const streamFn = await streamFnPromise;
|
|
72
|
+
return streamFn(...args);
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "anthropic-vertex",
|
|
3
|
+
"activation": {
|
|
4
|
+
"onStartup": false
|
|
5
|
+
},
|
|
6
|
+
"enabledByDefault": true,
|
|
7
|
+
"providers": ["anthropic-vertex"],
|
|
8
|
+
"providerCatalogEntry": "./provider-discovery.ts",
|
|
9
|
+
"syntheticAuthRefs": ["anthropic-vertex"],
|
|
10
|
+
"nonSecretAuthMarkers": ["gcp-vertex-credentials"],
|
|
11
|
+
"configSchema": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"properties": {}
|
|
15
|
+
}
|
|
16
|
+
}
|
package/index.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { definePluginEntry } from "autobot/plugin-sdk/plugin-entry";
|
|
2
|
+
import { readConfiguredProviderCatalogEntries } from "autobot/plugin-sdk/provider-catalog-shared";
|
|
3
|
+
import { NATIVE_ANTHROPIC_REPLAY_HOOKS } from "autobot/plugin-sdk/provider-model-shared";
|
|
4
|
+
import {
|
|
5
|
+
hasAnthropicVertexAvailableAuth,
|
|
6
|
+
mergeImplicitAnthropicVertexProvider,
|
|
7
|
+
resolveAnthropicVertexConfigApiKey,
|
|
8
|
+
resolveImplicitAnthropicVertexProvider,
|
|
9
|
+
} from "./api.js";
|
|
10
|
+
|
|
11
|
+
const PROVIDER_ID = "anthropic-vertex";
|
|
12
|
+
const GCP_VERTEX_CREDENTIALS_MARKER = "gcp-vertex-credentials";
|
|
13
|
+
|
|
14
|
+
export default definePluginEntry({
|
|
15
|
+
id: PROVIDER_ID,
|
|
16
|
+
name: "Anthropic Vertex Provider",
|
|
17
|
+
description: "Bundled Anthropic Vertex provider plugin",
|
|
18
|
+
register(api) {
|
|
19
|
+
api.registerProvider({
|
|
20
|
+
id: PROVIDER_ID,
|
|
21
|
+
label: "Anthropic Vertex",
|
|
22
|
+
docsPath: "/providers/models",
|
|
23
|
+
auth: [],
|
|
24
|
+
catalog: {
|
|
25
|
+
order: "simple",
|
|
26
|
+
run: async (ctx) => {
|
|
27
|
+
const implicit = resolveImplicitAnthropicVertexProvider({
|
|
28
|
+
env: ctx.env,
|
|
29
|
+
});
|
|
30
|
+
if (!implicit) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
provider: mergeImplicitAnthropicVertexProvider({
|
|
35
|
+
existing: ctx.config.models?.providers?.[PROVIDER_ID],
|
|
36
|
+
implicit,
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
resolveConfigApiKey: ({ env }) => resolveAnthropicVertexConfigApiKey(env),
|
|
42
|
+
...NATIVE_ANTHROPIC_REPLAY_HOOKS,
|
|
43
|
+
resolveSyntheticAuth: () => {
|
|
44
|
+
if (!hasAnthropicVertexAvailableAuth()) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
apiKey: GCP_VERTEX_CREDENTIALS_MARKER,
|
|
49
|
+
source: "gcp-vertex-credentials (ADC)",
|
|
50
|
+
mode: "api-key",
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
augmentModelCatalog: ({ config }) =>
|
|
54
|
+
readConfiguredProviderCatalogEntries({
|
|
55
|
+
config,
|
|
56
|
+
providerId: PROVIDER_ID,
|
|
57
|
+
}),
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gakr-gakr/anthropic-vertex-provider",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AutoBot Anthropic Vertex provider plugin",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/autobot/autobot"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@anthropic-ai/vertex-sdk": "0.16.0",
|
|
12
|
+
"@earendil-works/pi-agent-core": "0.75.1",
|
|
13
|
+
"@earendil-works/pi-ai": "0.75.1"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@gakr-gakr/plugin-sdk": "workspace:*"
|
|
17
|
+
},
|
|
18
|
+
"autobot": {
|
|
19
|
+
"extensions": [
|
|
20
|
+
"./index.ts"
|
|
21
|
+
],
|
|
22
|
+
"install": {
|
|
23
|
+
"npmSpec": "@gakr-gakr/anthropic-vertex-provider",
|
|
24
|
+
"defaultChoice": "npm",
|
|
25
|
+
"minHostVersion": ">=2026.5.12-beta.1"
|
|
26
|
+
},
|
|
27
|
+
"compat": {
|
|
28
|
+
"pluginApi": ">=2026.5.19"
|
|
29
|
+
},
|
|
30
|
+
"build": {
|
|
31
|
+
"autobotVersion": "2026.5.19",
|
|
32
|
+
"bundledDist": false
|
|
33
|
+
},
|
|
34
|
+
"release": {
|
|
35
|
+
"publishToClawHub": true,
|
|
36
|
+
"publishToNpm": true
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ModelDefinitionConfig,
|
|
3
|
+
ModelProviderConfig,
|
|
4
|
+
} from "autobot/plugin-sdk/provider-model-shared";
|
|
5
|
+
import { normalizeLowercaseStringOrEmpty } from "autobot/plugin-sdk/string-coerce-runtime";
|
|
6
|
+
import { resolveAnthropicVertexRegion } from "./region.js";
|
|
7
|
+
export const ANTHROPIC_VERTEX_DEFAULT_MODEL_ID = "claude-sonnet-4-6";
|
|
8
|
+
const ANTHROPIC_VERTEX_DEFAULT_CONTEXT_WINDOW = 1_000_000;
|
|
9
|
+
const GCP_VERTEX_CREDENTIALS_MARKER = "gcp-vertex-credentials";
|
|
10
|
+
|
|
11
|
+
function buildAnthropicVertexModel(params: {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
reasoning: boolean;
|
|
15
|
+
input: ModelDefinitionConfig["input"];
|
|
16
|
+
cost: ModelDefinitionConfig["cost"];
|
|
17
|
+
maxTokens: number;
|
|
18
|
+
}): ModelDefinitionConfig {
|
|
19
|
+
return {
|
|
20
|
+
id: params.id,
|
|
21
|
+
name: params.name,
|
|
22
|
+
reasoning: params.reasoning,
|
|
23
|
+
input: params.input,
|
|
24
|
+
cost: params.cost,
|
|
25
|
+
contextWindow: ANTHROPIC_VERTEX_DEFAULT_CONTEXT_WINDOW,
|
|
26
|
+
maxTokens: params.maxTokens,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function buildAnthropicVertexCatalog(): ModelDefinitionConfig[] {
|
|
31
|
+
return [
|
|
32
|
+
buildAnthropicVertexModel({
|
|
33
|
+
id: "claude-opus-4-6",
|
|
34
|
+
name: "Claude Opus 4.6",
|
|
35
|
+
reasoning: true,
|
|
36
|
+
input: ["text", "image"],
|
|
37
|
+
cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
38
|
+
maxTokens: 128000,
|
|
39
|
+
}),
|
|
40
|
+
buildAnthropicVertexModel({
|
|
41
|
+
id: ANTHROPIC_VERTEX_DEFAULT_MODEL_ID,
|
|
42
|
+
name: "Claude Sonnet 4.6",
|
|
43
|
+
reasoning: true,
|
|
44
|
+
input: ["text", "image"],
|
|
45
|
+
cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
46
|
+
maxTokens: 128000,
|
|
47
|
+
}),
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function buildAnthropicVertexProvider(params?: {
|
|
52
|
+
env?: NodeJS.ProcessEnv;
|
|
53
|
+
}): ModelProviderConfig {
|
|
54
|
+
const region = resolveAnthropicVertexRegion(params?.env);
|
|
55
|
+
const baseUrl =
|
|
56
|
+
normalizeLowercaseStringOrEmpty(region) === "global"
|
|
57
|
+
? "https://aiplatform.googleapis.com"
|
|
58
|
+
: `https://${region}-aiplatform.googleapis.com`;
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
baseUrl,
|
|
62
|
+
api: "anthropic-messages",
|
|
63
|
+
apiKey: GCP_VERTEX_CREDENTIALS_MARKER,
|
|
64
|
+
models: buildAnthropicVertexCatalog(),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { ProviderCatalogContext } from "autobot/plugin-sdk/provider-catalog-shared";
|
|
2
|
+
import type { ModelProviderConfig } from "autobot/plugin-sdk/provider-model-shared";
|
|
3
|
+
import { buildAnthropicVertexProvider } from "./provider-catalog.js";
|
|
4
|
+
import { hasAnthropicVertexAvailableAuth, resolveAnthropicVertexConfigApiKey } from "./region.js";
|
|
5
|
+
|
|
6
|
+
const PROVIDER_ID = "anthropic-vertex";
|
|
7
|
+
const GCP_VERTEX_CREDENTIALS_MARKER = "gcp-vertex-credentials";
|
|
8
|
+
|
|
9
|
+
type AnthropicVertexProviderPlugin = {
|
|
10
|
+
id: string;
|
|
11
|
+
label: string;
|
|
12
|
+
docsPath: string;
|
|
13
|
+
auth: [];
|
|
14
|
+
catalog: {
|
|
15
|
+
order: "simple";
|
|
16
|
+
run: (ctx: ProviderCatalogContext) => ReturnType<typeof runAnthropicVertexCatalog>;
|
|
17
|
+
};
|
|
18
|
+
resolveConfigApiKey: (params: { env: NodeJS.ProcessEnv }) => string | undefined;
|
|
19
|
+
resolveSyntheticAuth: () =>
|
|
20
|
+
| {
|
|
21
|
+
apiKey: string;
|
|
22
|
+
source: string;
|
|
23
|
+
mode: "api-key";
|
|
24
|
+
}
|
|
25
|
+
| undefined;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function mergeImplicitAnthropicVertexProvider(params: {
|
|
29
|
+
existing?: ModelProviderConfig;
|
|
30
|
+
implicit: ModelProviderConfig;
|
|
31
|
+
}) {
|
|
32
|
+
const { existing, implicit } = params;
|
|
33
|
+
if (!existing) {
|
|
34
|
+
return implicit;
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
...implicit,
|
|
38
|
+
...existing,
|
|
39
|
+
models:
|
|
40
|
+
Array.isArray(existing.models) && existing.models.length > 0
|
|
41
|
+
? existing.models
|
|
42
|
+
: implicit.models,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function resolveImplicitAnthropicVertexProvider(params?: { env?: NodeJS.ProcessEnv }) {
|
|
47
|
+
const env = params?.env ?? process.env;
|
|
48
|
+
if (!hasAnthropicVertexAvailableAuth(env)) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return buildAnthropicVertexProvider({ env });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function runAnthropicVertexCatalog(ctx: ProviderCatalogContext) {
|
|
56
|
+
const implicit = resolveImplicitAnthropicVertexProvider({
|
|
57
|
+
env: ctx.env,
|
|
58
|
+
});
|
|
59
|
+
if (!implicit) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
provider: mergeImplicitAnthropicVertexProvider({
|
|
64
|
+
existing: ctx.config.models?.providers?.[PROVIDER_ID],
|
|
65
|
+
implicit,
|
|
66
|
+
}),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const anthropicVertexProviderDiscovery: AnthropicVertexProviderPlugin = {
|
|
71
|
+
id: PROVIDER_ID,
|
|
72
|
+
label: "Anthropic Vertex",
|
|
73
|
+
docsPath: "/providers/models",
|
|
74
|
+
auth: [],
|
|
75
|
+
catalog: {
|
|
76
|
+
order: "simple",
|
|
77
|
+
run: runAnthropicVertexCatalog,
|
|
78
|
+
},
|
|
79
|
+
resolveConfigApiKey: ({ env }) => resolveAnthropicVertexConfigApiKey(env),
|
|
80
|
+
resolveSyntheticAuth: () => {
|
|
81
|
+
if (!hasAnthropicVertexAvailableAuth()) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
apiKey: GCP_VERTEX_CREDENTIALS_MARKER,
|
|
86
|
+
source: "gcp-vertex-credentials (ADC)",
|
|
87
|
+
mode: "api-key",
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export default anthropicVertexProviderDiscovery;
|
package/region.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { homedir, platform } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { resolveProviderEndpoint } from "autobot/plugin-sdk/provider-http";
|
|
5
|
+
import { normalizeLowercaseStringOrEmpty } from "autobot/plugin-sdk/string-coerce-runtime";
|
|
6
|
+
|
|
7
|
+
const ANTHROPIC_VERTEX_DEFAULT_REGION = "global";
|
|
8
|
+
const ANTHROPIC_VERTEX_REGION_RE = /^[a-z0-9-]+$/;
|
|
9
|
+
const GCP_VERTEX_CREDENTIALS_MARKER = "gcp-vertex-credentials";
|
|
10
|
+
|
|
11
|
+
type AdcProjectFile = {
|
|
12
|
+
project_id?: unknown;
|
|
13
|
+
quota_project_id?: unknown;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function normalizeOptionalSecretInput(value: unknown): string | undefined {
|
|
17
|
+
if (typeof value !== "string") {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const trimmed = value.trim();
|
|
21
|
+
return trimmed || undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function resolveAnthropicVertexRegion(env: NodeJS.ProcessEnv = process.env): string {
|
|
25
|
+
const region =
|
|
26
|
+
normalizeOptionalSecretInput(env.GOOGLE_CLOUD_LOCATION) ||
|
|
27
|
+
normalizeOptionalSecretInput(env.CLOUD_ML_REGION);
|
|
28
|
+
|
|
29
|
+
return region && ANTHROPIC_VERTEX_REGION_RE.test(region)
|
|
30
|
+
? region
|
|
31
|
+
: ANTHROPIC_VERTEX_DEFAULT_REGION;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function resolveAnthropicVertexProjectId(
|
|
35
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
36
|
+
): string | undefined {
|
|
37
|
+
return (
|
|
38
|
+
normalizeOptionalSecretInput(env.ANTHROPIC_VERTEX_PROJECT_ID) ||
|
|
39
|
+
normalizeOptionalSecretInput(env.GOOGLE_CLOUD_PROJECT) ||
|
|
40
|
+
normalizeOptionalSecretInput(env.GOOGLE_CLOUD_PROJECT_ID) ||
|
|
41
|
+
resolveAnthropicVertexProjectIdFromAdc(env)
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function resolveAnthropicVertexRegionFromBaseUrl(baseUrl?: string): string | undefined {
|
|
46
|
+
const endpoint = resolveProviderEndpoint(baseUrl);
|
|
47
|
+
return endpoint.endpointClass === "google-vertex" ? endpoint.googleVertexRegion : undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function resolveAnthropicVertexClientRegion(params?: {
|
|
51
|
+
baseUrl?: string;
|
|
52
|
+
env?: NodeJS.ProcessEnv;
|
|
53
|
+
}): string {
|
|
54
|
+
return (
|
|
55
|
+
resolveAnthropicVertexRegionFromBaseUrl(params?.baseUrl) ||
|
|
56
|
+
resolveAnthropicVertexRegion(params?.env)
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function hasAnthropicVertexMetadataServerAdc(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
61
|
+
const explicitMetadataOptIn = normalizeOptionalSecretInput(env.ANTHROPIC_VERTEX_USE_GCP_METADATA);
|
|
62
|
+
return (
|
|
63
|
+
explicitMetadataOptIn === "1" ||
|
|
64
|
+
normalizeLowercaseStringOrEmpty(explicitMetadataOptIn) === "true"
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function resolveAnthropicVertexHomeDir(env: NodeJS.ProcessEnv = process.env): string {
|
|
69
|
+
return (
|
|
70
|
+
normalizeOptionalSecretInput(env.HOME) ||
|
|
71
|
+
normalizeOptionalSecretInput(env.USERPROFILE) ||
|
|
72
|
+
homedir()
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function resolveAnthropicVertexDefaultAdcPath(env: NodeJS.ProcessEnv = process.env): string {
|
|
77
|
+
return platform() === "win32"
|
|
78
|
+
? join(
|
|
79
|
+
normalizeOptionalSecretInput(env.APPDATA) ??
|
|
80
|
+
join(resolveAnthropicVertexHomeDir(env), "AppData", "Roaming"),
|
|
81
|
+
"gcloud",
|
|
82
|
+
"application_default_credentials.json",
|
|
83
|
+
)
|
|
84
|
+
: join(
|
|
85
|
+
resolveAnthropicVertexHomeDir(env),
|
|
86
|
+
".config",
|
|
87
|
+
"gcloud",
|
|
88
|
+
"application_default_credentials.json",
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function resolveAnthropicVertexAdcCredentialsPathCandidate(
|
|
93
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
94
|
+
): string | undefined {
|
|
95
|
+
const explicit = normalizeOptionalSecretInput(env.GOOGLE_APPLICATION_CREDENTIALS);
|
|
96
|
+
if (explicit) {
|
|
97
|
+
return explicit;
|
|
98
|
+
}
|
|
99
|
+
return resolveAnthropicVertexDefaultAdcPath(env);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function canReadAnthropicVertexAdc(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
103
|
+
const credentialsPath = resolveAnthropicVertexAdcCredentialsPathCandidate(env);
|
|
104
|
+
if (!credentialsPath) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
readFileSync(credentialsPath, "utf8");
|
|
109
|
+
return true;
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function resolveAnthropicVertexProjectIdFromAdc(
|
|
116
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
117
|
+
): string | undefined {
|
|
118
|
+
const credentialsPath = resolveAnthropicVertexAdcCredentialsPathCandidate(env);
|
|
119
|
+
if (!credentialsPath) {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const parsed = JSON.parse(readFileSync(credentialsPath, "utf8")) as AdcProjectFile;
|
|
124
|
+
return (
|
|
125
|
+
normalizeOptionalSecretInput(parsed.project_id) ||
|
|
126
|
+
normalizeOptionalSecretInput(parsed.quota_project_id)
|
|
127
|
+
);
|
|
128
|
+
} catch {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function hasAnthropicVertexCredentials(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
134
|
+
return hasAnthropicVertexMetadataServerAdc(env) || canReadAnthropicVertexAdc(env);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function hasAnthropicVertexAvailableAuth(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
138
|
+
return hasAnthropicVertexCredentials(env);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function resolveAnthropicVertexConfigApiKey(
|
|
142
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
143
|
+
): string | undefined {
|
|
144
|
+
return hasAnthropicVertexAvailableAuth(env) ? GCP_VERTEX_CREDENTIALS_MARKER : undefined;
|
|
145
|
+
}
|
package/setup-api.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { definePluginEntry } from "autobot/plugin-sdk/plugin-entry";
|
|
2
|
+
import { resolveAnthropicVertexConfigApiKey } from "./region.js";
|
|
3
|
+
|
|
4
|
+
export default definePluginEntry({
|
|
5
|
+
id: "anthropic-vertex",
|
|
6
|
+
name: "Anthropic Vertex Setup",
|
|
7
|
+
description: "Lightweight Anthropic Vertex setup hooks",
|
|
8
|
+
register(api) {
|
|
9
|
+
api.registerProvider({
|
|
10
|
+
id: "anthropic-vertex",
|
|
11
|
+
label: "Anthropic Vertex",
|
|
12
|
+
auth: [],
|
|
13
|
+
resolveConfigApiKey: ({ env }) => resolveAnthropicVertexConfigApiKey(env),
|
|
14
|
+
});
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { AnthropicVertex as AnthropicVertexSdk } from "@anthropic-ai/vertex-sdk";
|
|
2
|
+
import type { StreamFn } from "@earendil-works/pi-agent-core";
|
|
3
|
+
import {
|
|
4
|
+
streamAnthropic as streamAnthropicDefault,
|
|
5
|
+
type AnthropicOptions,
|
|
6
|
+
type Model,
|
|
7
|
+
} from "@earendil-works/pi-ai";
|
|
8
|
+
import {
|
|
9
|
+
applyAnthropicPayloadPolicyToParams,
|
|
10
|
+
resolveAnthropicPayloadPolicy,
|
|
11
|
+
} from "autobot/plugin-sdk/provider-stream-shared";
|
|
12
|
+
import { resolveAnthropicVertexClientRegion, resolveAnthropicVertexProjectId } from "./region.js";
|
|
13
|
+
|
|
14
|
+
type AnthropicVertexEffort = NonNullable<AnthropicOptions["effort"]>;
|
|
15
|
+
type AnthropicVertexAdaptiveEffort = AnthropicVertexEffort | "xhigh";
|
|
16
|
+
type AnthropicVertexClientOptions = {
|
|
17
|
+
baseURL?: string;
|
|
18
|
+
projectId?: string;
|
|
19
|
+
region: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type AnthropicVertexStreamDeps = {
|
|
23
|
+
AnthropicVertex: new (options: AnthropicVertexClientOptions) => unknown;
|
|
24
|
+
streamAnthropic: typeof streamAnthropicDefault;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const defaultAnthropicVertexStreamDeps: AnthropicVertexStreamDeps = {
|
|
28
|
+
AnthropicVertex: AnthropicVertexSdk as AnthropicVertexStreamDeps["AnthropicVertex"],
|
|
29
|
+
streamAnthropic: streamAnthropicDefault,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function isClaudeOpus47Model(modelId: string): boolean {
|
|
33
|
+
return modelId.includes("opus-4-7") || modelId.includes("opus-4.7");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isClaudeOpus46Model(modelId: string): boolean {
|
|
37
|
+
return modelId.includes("opus-4-6") || modelId.includes("opus-4.6");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function supportsAdaptiveThinking(modelId: string): boolean {
|
|
41
|
+
return (
|
|
42
|
+
isClaudeOpus47Model(modelId) ||
|
|
43
|
+
isClaudeOpus46Model(modelId) ||
|
|
44
|
+
modelId.includes("sonnet-4-6") ||
|
|
45
|
+
modelId.includes("sonnet-4.6")
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function mapAnthropicAdaptiveEffort(
|
|
50
|
+
reasoning: string,
|
|
51
|
+
modelId: string,
|
|
52
|
+
): AnthropicVertexAdaptiveEffort {
|
|
53
|
+
const effortMap: Record<string, AnthropicVertexAdaptiveEffort> = {
|
|
54
|
+
minimal: "low",
|
|
55
|
+
low: "low",
|
|
56
|
+
medium: "medium",
|
|
57
|
+
high: "high",
|
|
58
|
+
xhigh: isClaudeOpus47Model(modelId) ? "xhigh" : isClaudeOpus46Model(modelId) ? "max" : "high",
|
|
59
|
+
};
|
|
60
|
+
return effortMap[reasoning] ?? "high";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resolveAnthropicVertexMaxTokens(params: {
|
|
64
|
+
modelMaxTokens: number | undefined;
|
|
65
|
+
requestedMaxTokens: number | undefined;
|
|
66
|
+
}): number | undefined {
|
|
67
|
+
const modelMax =
|
|
68
|
+
typeof params.modelMaxTokens === "number" &&
|
|
69
|
+
Number.isFinite(params.modelMaxTokens) &&
|
|
70
|
+
params.modelMaxTokens > 0
|
|
71
|
+
? Math.floor(params.modelMaxTokens)
|
|
72
|
+
: undefined;
|
|
73
|
+
const requested =
|
|
74
|
+
typeof params.requestedMaxTokens === "number" &&
|
|
75
|
+
Number.isFinite(params.requestedMaxTokens) &&
|
|
76
|
+
params.requestedMaxTokens > 0
|
|
77
|
+
? Math.floor(params.requestedMaxTokens)
|
|
78
|
+
: undefined;
|
|
79
|
+
|
|
80
|
+
if (modelMax !== undefined && requested !== undefined) {
|
|
81
|
+
return Math.min(requested, modelMax);
|
|
82
|
+
}
|
|
83
|
+
return requested ?? modelMax;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function createAnthropicVertexOnPayload(params: {
|
|
87
|
+
model: { api: string; baseUrl?: string; provider: string };
|
|
88
|
+
cacheRetention: AnthropicOptions["cacheRetention"] | undefined;
|
|
89
|
+
onPayload: AnthropicOptions["onPayload"] | undefined;
|
|
90
|
+
}): NonNullable<AnthropicOptions["onPayload"]> {
|
|
91
|
+
const policy = resolveAnthropicPayloadPolicy({
|
|
92
|
+
provider: params.model.provider,
|
|
93
|
+
api: params.model.api,
|
|
94
|
+
baseUrl: params.model.baseUrl,
|
|
95
|
+
cacheRetention: params.cacheRetention,
|
|
96
|
+
enableCacheControl: true,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
function applyPolicy(payload: unknown): unknown {
|
|
100
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
101
|
+
applyAnthropicPayloadPolicyToParams(payload as Record<string, unknown>, policy);
|
|
102
|
+
}
|
|
103
|
+
return payload;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return async (payload, model) => {
|
|
107
|
+
const shapedPayload = applyPolicy(payload);
|
|
108
|
+
const nextPayload = await params.onPayload?.(shapedPayload, model);
|
|
109
|
+
if (nextPayload === undefined || nextPayload === shapedPayload) {
|
|
110
|
+
return shapedPayload;
|
|
111
|
+
}
|
|
112
|
+
return applyPolicy(nextPayload);
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Create a StreamFn that routes through pi-ai's `streamAnthropic` with an
|
|
118
|
+
* injected `AnthropicVertex` client. All streaming, message conversion, and
|
|
119
|
+
* event handling is handled by pi-ai — we only supply the GCP-authenticated
|
|
120
|
+
* client and map SimpleStreamOptions → AnthropicOptions.
|
|
121
|
+
*/
|
|
122
|
+
export function createAnthropicVertexStreamFn(
|
|
123
|
+
projectId: string | undefined,
|
|
124
|
+
region: string,
|
|
125
|
+
baseURL?: string,
|
|
126
|
+
deps: AnthropicVertexStreamDeps = defaultAnthropicVertexStreamDeps,
|
|
127
|
+
): StreamFn {
|
|
128
|
+
const client = new deps.AnthropicVertex({
|
|
129
|
+
region,
|
|
130
|
+
...(baseURL ? { baseURL } : {}),
|
|
131
|
+
...(projectId ? { projectId } : {}),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return (model, context, options) => {
|
|
135
|
+
const transportModel = model as Model<"anthropic-messages"> & {
|
|
136
|
+
api: string;
|
|
137
|
+
baseUrl?: string;
|
|
138
|
+
provider: string;
|
|
139
|
+
};
|
|
140
|
+
const maxTokens = resolveAnthropicVertexMaxTokens({
|
|
141
|
+
modelMaxTokens: transportModel.maxTokens,
|
|
142
|
+
requestedMaxTokens: options?.maxTokens,
|
|
143
|
+
});
|
|
144
|
+
const opts: AnthropicOptions = {
|
|
145
|
+
client: client as AnthropicOptions["client"],
|
|
146
|
+
temperature: options?.temperature,
|
|
147
|
+
...(maxTokens !== undefined ? { maxTokens } : {}),
|
|
148
|
+
signal: options?.signal,
|
|
149
|
+
cacheRetention: options?.cacheRetention,
|
|
150
|
+
sessionId: options?.sessionId,
|
|
151
|
+
headers: options?.headers,
|
|
152
|
+
onPayload: createAnthropicVertexOnPayload({
|
|
153
|
+
model: transportModel,
|
|
154
|
+
cacheRetention: options?.cacheRetention,
|
|
155
|
+
onPayload: options?.onPayload,
|
|
156
|
+
}),
|
|
157
|
+
maxRetryDelayMs: options?.maxRetryDelayMs,
|
|
158
|
+
metadata: options?.metadata,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
if (options?.reasoning) {
|
|
162
|
+
if (supportsAdaptiveThinking(model.id)) {
|
|
163
|
+
opts.thinkingEnabled = true;
|
|
164
|
+
opts.effort = mapAnthropicAdaptiveEffort(
|
|
165
|
+
options.reasoning,
|
|
166
|
+
model.id,
|
|
167
|
+
) as AnthropicVertexEffort;
|
|
168
|
+
} else {
|
|
169
|
+
opts.thinkingEnabled = true;
|
|
170
|
+
const budgets = options.thinkingBudgets;
|
|
171
|
+
opts.thinkingBudgetTokens =
|
|
172
|
+
(budgets && options.reasoning in budgets
|
|
173
|
+
? budgets[options.reasoning as keyof typeof budgets]
|
|
174
|
+
: undefined) ?? 10000;
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
opts.thinkingEnabled = false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return deps.streamAnthropic(transportModel, context, opts);
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function resolveAnthropicVertexSdkBaseUrl(baseUrl?: string): string | undefined {
|
|
185
|
+
const trimmed = baseUrl?.trim();
|
|
186
|
+
if (!trimmed) {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const url = new URL(trimmed);
|
|
192
|
+
const normalizedPath = url.pathname.replace(/\/+$/, "");
|
|
193
|
+
if (!normalizedPath || normalizedPath === "") {
|
|
194
|
+
url.pathname = "/v1";
|
|
195
|
+
return url.toString().replace(/\/$/, "");
|
|
196
|
+
}
|
|
197
|
+
if (!normalizedPath.endsWith("/v1")) {
|
|
198
|
+
url.pathname = `${normalizedPath}/v1`;
|
|
199
|
+
return url.toString().replace(/\/$/, "");
|
|
200
|
+
}
|
|
201
|
+
return trimmed;
|
|
202
|
+
} catch {
|
|
203
|
+
return trimmed;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function createAnthropicVertexStreamFnForModel(
|
|
208
|
+
model: { baseUrl?: string },
|
|
209
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
210
|
+
deps?: AnthropicVertexStreamDeps,
|
|
211
|
+
): StreamFn {
|
|
212
|
+
return createAnthropicVertexStreamFn(
|
|
213
|
+
resolveAnthropicVertexProjectId(env),
|
|
214
|
+
resolveAnthropicVertexClientRegion({
|
|
215
|
+
baseUrl: model.baseUrl,
|
|
216
|
+
env,
|
|
217
|
+
}),
|
|
218
|
+
resolveAnthropicVertexSdkBaseUrl(model.baseUrl),
|
|
219
|
+
deps,
|
|
220
|
+
);
|
|
221
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../tsconfig.package-boundary.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "."
|
|
5
|
+
},
|
|
6
|
+
"include": ["./*.ts", "./src/**/*.ts"],
|
|
7
|
+
"exclude": [
|
|
8
|
+
"./**/*.test.ts",
|
|
9
|
+
"./dist/**",
|
|
10
|
+
"./node_modules/**",
|
|
11
|
+
"./src/test-support/**",
|
|
12
|
+
"./src/**/*test-helpers.ts",
|
|
13
|
+
"./src/**/*test-harness.ts",
|
|
14
|
+
"./src/**/*test-support.ts"
|
|
15
|
+
]
|
|
16
|
+
}
|