@use-lattice/litmus 0.121.3
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/LICENSE +19 -0
- package/dist/src/accounts-Bt1oJb1Z.cjs +219 -0
- package/dist/src/accounts-DjOU8Rm3.js +178 -0
- package/dist/src/agentic-utils-D03IiXQc.js +153 -0
- package/dist/src/agentic-utils-Dh7xaMQM.cjs +180 -0
- package/dist/src/agents-C6BIMlZa.js +231 -0
- package/dist/src/agents-DvIpNX1L.cjs +666 -0
- package/dist/src/agents-ZP0RP9vV.cjs +231 -0
- package/dist/src/agents-maJXdjbR.js +665 -0
- package/dist/src/aimlapi-BTbQjG2E.cjs +30 -0
- package/dist/src/aimlapi-CwMxqfXP.js +30 -0
- package/dist/src/audio-BBUdvsde.cjs +97 -0
- package/dist/src/audio-D5DPZ7I-.js +97 -0
- package/dist/src/base-BEysXrkq.cjs +222 -0
- package/dist/src/base-C451JQfq.js +193 -0
- package/dist/src/blobs-BY8MDmpo.js +230 -0
- package/dist/src/blobs-BgcNn97m.cjs +256 -0
- package/dist/src/cache-BBE_lsTA.cjs +4 -0
- package/dist/src/cache-BkrqU5Ba.js +237 -0
- package/dist/src/cache-DsCxFlsZ.cjs +297 -0
- package/dist/src/chat-CPJWDP6a.cjs +289 -0
- package/dist/src/chat-CXX3xzkk.cjs +811 -0
- package/dist/src/chat-CcDgZFJ4.js +787 -0
- package/dist/src/chat-Dz5ZeGO2.js +289 -0
- package/dist/src/chatkit-Dw0mKkML.cjs +1158 -0
- package/dist/src/chatkit-swAIVuea.js +1157 -0
- package/dist/src/chunk-DEq-mXcV.js +15 -0
- package/dist/src/claude-agent-sdk-BXZJtOg6.js +379 -0
- package/dist/src/claude-agent-sdk-CkfyjDoG.cjs +383 -0
- package/dist/src/cloudflare-ai-BzpJcqUH.js +161 -0
- package/dist/src/cloudflare-ai-Cmy_R1y2.cjs +161 -0
- package/dist/src/cloudflare-gateway-B9tVQKok.cjs +272 -0
- package/dist/src/cloudflare-gateway-DrD3ew3H.js +272 -0
- package/dist/src/codex-sdk-Dezj9Nwm.js +1056 -0
- package/dist/src/codex-sdk-Dl9D4k5B.cjs +1060 -0
- package/dist/src/cometapi-C-9YvCHC.js +54 -0
- package/dist/src/cometapi-DHgDKoO2.cjs +54 -0
- package/dist/src/completion-B8Ctyxpr.js +120 -0
- package/dist/src/completion-Cxrt08sj.cjs +131 -0
- package/dist/src/createHash-BwgE13yv.cjs +27 -0
- package/dist/src/createHash-DmPQkvBh.js +15 -0
- package/dist/src/docker-BiqcTwLv.js +80 -0
- package/dist/src/docker-C7tEJnP-.cjs +80 -0
- package/dist/src/esm-C62Zofr1.cjs +409 -0
- package/dist/src/esm-DMVc93eh.js +379 -0
- package/dist/src/evalResult-C3NJPQOo.cjs +301 -0
- package/dist/src/evalResult-C7JJAPBb.js +295 -0
- package/dist/src/evalResult-DoVTZZWI.cjs +2 -0
- package/dist/src/extractor-DnMD3fwt.cjs +391 -0
- package/dist/src/extractor-DtlL28vL.js +374 -0
- package/dist/src/fetch-BTxakTSg.cjs +1133 -0
- package/dist/src/fetch-DQckpUFz.js +928 -0
- package/dist/src/fileExtensions-DnqA1y9x.js +85 -0
- package/dist/src/fileExtensions-bYh77CN8.cjs +114 -0
- package/dist/src/genaiTracer-CyZrmaK0.cjs +268 -0
- package/dist/src/genaiTracer-D3fD9dNV.js +256 -0
- package/dist/src/graders-BNscxFrU.js +13644 -0
- package/dist/src/graders-D2oE9Msq.js +2 -0
- package/dist/src/graders-c0Ez_w-9.cjs +2 -0
- package/dist/src/graders-d0F2M3e9.cjs +14056 -0
- package/dist/src/image-0ZhE0VlR.cjs +280 -0
- package/dist/src/image-CWE1pdNv.js +257 -0
- package/dist/src/image-D9ZK6hwL.js +163 -0
- package/dist/src/image-DKZgZITg.cjs +163 -0
- package/dist/src/index.cjs +11366 -0
- package/dist/src/index.d.cts +19640 -0
- package/dist/src/index.d.ts +19641 -0
- package/dist/src/index.js +11306 -0
- package/dist/src/invariant-Ddh24eXh.js +25 -0
- package/dist/src/invariant-kfQ8Bu82.cjs +30 -0
- package/dist/src/knowledgeBase-BgPyGFUd.cjs +122 -0
- package/dist/src/knowledgeBase-DyHilYaP.js +122 -0
- package/dist/src/litellm-CyMeneHS.js +135 -0
- package/dist/src/litellm-DWDF73yF.cjs +135 -0
- package/dist/src/logger-C40ZGil9.js +717 -0
- package/dist/src/logger-DyfK9PBt.cjs +917 -0
- package/dist/src/luma-ray-BAU9X_ep.cjs +315 -0
- package/dist/src/luma-ray-nwVseBbv.js +313 -0
- package/dist/src/messages-B5ADWTTv.js +245 -0
- package/dist/src/messages-BCnZfqrS.cjs +257 -0
- package/dist/src/meteor-DLZZ3osF.cjs +134 -0
- package/dist/src/meteor-DUiCJRC-.js +134 -0
- package/dist/src/modelslab-00cveB8L.cjs +163 -0
- package/dist/src/modelslab-D9sCU_L7.js +163 -0
- package/dist/src/nova-reel-CTapvqYH.js +276 -0
- package/dist/src/nova-reel-DlWuuroF.cjs +278 -0
- package/dist/src/nova-sonic-5UPWfeMv.cjs +363 -0
- package/dist/src/nova-sonic-BhSwQNym.js +363 -0
- package/dist/src/openai-BWrJK9d8.cjs +52 -0
- package/dist/src/openai-DumO8WQn.js +47 -0
- package/dist/src/openclaw-B8brrjC_.cjs +577 -0
- package/dist/src/openclaw-Bkayww9q.js +571 -0
- package/dist/src/opencode-sdk-7xjoDNiM.cjs +562 -0
- package/dist/src/opencode-sdk-SGwAPxht.js +558 -0
- package/dist/src/otlpReceiver-CoAHfAN9.cjs +15 -0
- package/dist/src/otlpReceiver-oO3EQwI9.js +14 -0
- package/dist/src/providerRegistry-4yjhaEM8.js +45 -0
- package/dist/src/providerRegistry-DhV4rJIc.cjs +50 -0
- package/dist/src/providers-B5RJVG-7.cjs +33609 -0
- package/dist/src/providers-BdmZCLzV.js +33262 -0
- package/dist/src/providers-CxtRxn8e.js +2 -0
- package/dist/src/providers-DnQLNbx1.cjs +3 -0
- package/dist/src/pythonUtils-BD0druiM.cjs +275 -0
- package/dist/src/pythonUtils-IBhn5YGR.js +249 -0
- package/dist/src/quiverai-BDOwZBsM.cjs +213 -0
- package/dist/src/quiverai-D3JTF5lD.js +213 -0
- package/dist/src/responses-B2LCDCXZ.js +667 -0
- package/dist/src/responses-BvNm4Xv9.cjs +685 -0
- package/dist/src/rubyUtils-B0NwnfpY.cjs +245 -0
- package/dist/src/rubyUtils-BroxzZ7c.cjs +2 -0
- package/dist/src/rubyUtils-hqVw5UvJ.js +222 -0
- package/dist/src/sagemaker-Cno2V-Sx.js +689 -0
- package/dist/src/sagemaker-fV_KUgs5.cjs +691 -0
- package/dist/src/server-BOuAXb06.cjs +238 -0
- package/dist/src/server-CtI-EWzm.cjs +2 -0
- package/dist/src/server-Cy3DZymt.js +189 -0
- package/dist/src/slack-CP8xBePa.js +135 -0
- package/dist/src/slack-DSQ1yXVb.cjs +135 -0
- package/dist/src/store-BwDDaBjb.cjs +246 -0
- package/dist/src/store-DcbLC593.cjs +2 -0
- package/dist/src/store-IGpqMIkv.js +240 -0
- package/dist/src/tables-3Q2cL7So.cjs +373 -0
- package/dist/src/tables-Bi2fjr4W.js +288 -0
- package/dist/src/telemetry-Bg2WqF79.js +161 -0
- package/dist/src/telemetry-D0x6u5kX.cjs +166 -0
- package/dist/src/telemetry-DXNimrI0.cjs +2 -0
- package/dist/src/text-B_UCRPp2.js +22 -0
- package/dist/src/text-CW1cyrwj.cjs +33 -0
- package/dist/src/tokenUsageUtils-NYT-WKS6.js +138 -0
- package/dist/src/tokenUsageUtils-bVa1ga6f.cjs +173 -0
- package/dist/src/transcription-Cl_W16Pr.js +122 -0
- package/dist/src/transcription-yt1EecY8.cjs +124 -0
- package/dist/src/transform-BCtGrl_W.cjs +228 -0
- package/dist/src/transform-Bv6gG2MJ.cjs +1688 -0
- package/dist/src/transform-CY1wbpRy.js +1507 -0
- package/dist/src/transform-DU8rUL9P.cjs +2 -0
- package/dist/src/transform-yWaShiKr.js +216 -0
- package/dist/src/transformersAvailability-BGkzavwb.js +35 -0
- package/dist/src/transformersAvailability-DKoRtQLy.cjs +35 -0
- package/dist/src/types-5aqHpBwE.cjs +3769 -0
- package/dist/src/types-Bn6D9c4U.js +3300 -0
- package/dist/src/util-BkKlTkI2.js +293 -0
- package/dist/src/util-CTh0bfOm.cjs +1119 -0
- package/dist/src/util-D17oBwo7.cjs +328 -0
- package/dist/src/util-DsS_-v4p.js +613 -0
- package/dist/src/util-DuntT1Ga.js +951 -0
- package/dist/src/util-aWjdCYMI.cjs +667 -0
- package/dist/src/utils-CisQwpjA.js +94 -0
- package/dist/src/utils-yWamDvmz.cjs +123 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/drizzle/0000_lush_hellion.sql +36 -0
- package/drizzle/0001_wide_calypso.sql +3 -0
- package/drizzle/0002_tidy_juggernaut.sql +1 -0
- package/drizzle/0003_lively_naoko.sql +8 -0
- package/drizzle/0004_minor_peter_quill.sql +19 -0
- package/drizzle/0005_silky_millenium_guard.sql +2 -0
- package/drizzle/0006_harsh_caretaker.sql +42 -0
- package/drizzle/0007_cloudy_wong.sql +1 -0
- package/drizzle/0008_broad_boomer.sql +2 -0
- package/drizzle/0009_strong_marten_broadcloak.sql +19 -0
- package/drizzle/0010_needy_bishop.sql +11 -0
- package/drizzle/0011_moaning_millenium_guard.sql +1 -0
- package/drizzle/0012_late_marten_broadcloak.sql +2 -0
- package/drizzle/0013_previous_dormammu.sql +9 -0
- package/drizzle/0014_lazy_captain_universe.sql +2 -0
- package/drizzle/0015_zippy_wallop.sql +29 -0
- package/drizzle/0016_jazzy_zemo.sql +2 -0
- package/drizzle/0017_reflective_praxagora.sql +4 -0
- package/drizzle/0018_fat_vanisher.sql +22 -0
- package/drizzle/0019_new_clint_barton.sql +8 -0
- package/drizzle/0020_skinny_maverick.sql +1 -0
- package/drizzle/0021_mysterious_madelyne_pryor.sql +13 -0
- package/drizzle/0022_sleepy_ultimo.sql +25 -0
- package/drizzle/0023_wooden_mandrill.sql +2 -0
- package/drizzle/AGENTS.md +68 -0
- package/drizzle/CLAUDE.md +1 -0
- package/drizzle/meta/0000_snapshot.json +221 -0
- package/drizzle/meta/0001_snapshot.json +214 -0
- package/drizzle/meta/0002_snapshot.json +221 -0
- package/drizzle/meta/0005_snapshot.json +369 -0
- package/drizzle/meta/0006_snapshot.json +638 -0
- package/drizzle/meta/0007_snapshot.json +640 -0
- package/drizzle/meta/0008_snapshot.json +649 -0
- package/drizzle/meta/0009_snapshot.json +554 -0
- package/drizzle/meta/0010_snapshot.json +619 -0
- package/drizzle/meta/0011_snapshot.json +627 -0
- package/drizzle/meta/0012_snapshot.json +639 -0
- package/drizzle/meta/0013_snapshot.json +717 -0
- package/drizzle/meta/0014_snapshot.json +717 -0
- package/drizzle/meta/0015_snapshot.json +897 -0
- package/drizzle/meta/0016_snapshot.json +1031 -0
- package/drizzle/meta/0018_snapshot.json +1210 -0
- package/drizzle/meta/0019_snapshot.json +1165 -0
- package/drizzle/meta/0020_snapshot.json +1232 -0
- package/drizzle/meta/0021_snapshot.json +1311 -0
- package/drizzle/meta/0022_snapshot.json +1481 -0
- package/drizzle/meta/0023_snapshot.json +1496 -0
- package/drizzle/meta/_journal.json +174 -0
- package/package.json +240 -0
|
@@ -0,0 +1,1507 @@
|
|
|
1
|
+
import { f as getAjv, r as logger, x as getEnvString } from "./logger-C40ZGil9.js";
|
|
2
|
+
import { c as maybeLoadFromExternalFile, h as renderVarsInObject, v as getNunjucksEngine } from "./util-DuntT1Ga.js";
|
|
3
|
+
import { S as parseChatPrompt, g as calculateCost, t as fetchWithProxy } from "./fetch-DQckpUFz.js";
|
|
4
|
+
import Clone from "rfdc";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import crypto from "crypto";
|
|
7
|
+
//#region src/util/oauth.ts
|
|
8
|
+
/**
|
|
9
|
+
* Buffer time before token expiry to trigger proactive refresh (60 seconds)
|
|
10
|
+
*/
|
|
11
|
+
const TOKEN_REFRESH_BUFFER_MS = 6e4;
|
|
12
|
+
/**
|
|
13
|
+
* Fetch an OAuth token from a token endpoint.
|
|
14
|
+
* Handles both client_credentials and password grant types.
|
|
15
|
+
*
|
|
16
|
+
* @param config - OAuth configuration with rendered/resolved values
|
|
17
|
+
* @returns Token and expiration timestamp
|
|
18
|
+
*/
|
|
19
|
+
async function fetchOAuthToken(config) {
|
|
20
|
+
const now = Date.now();
|
|
21
|
+
logger.debug("[OAuth] Fetching new token");
|
|
22
|
+
const tokenRequestBody = new URLSearchParams();
|
|
23
|
+
tokenRequestBody.append("grant_type", config.grantType);
|
|
24
|
+
if (config.clientId) tokenRequestBody.append("client_id", config.clientId);
|
|
25
|
+
if (config.clientSecret) tokenRequestBody.append("client_secret", config.clientSecret);
|
|
26
|
+
if (config.grantType === "password") {
|
|
27
|
+
if (config.username) tokenRequestBody.append("username", config.username);
|
|
28
|
+
if (config.password) tokenRequestBody.append("password", config.password);
|
|
29
|
+
}
|
|
30
|
+
if (config.scopes && config.scopes.length > 0) tokenRequestBody.append("scope", config.scopes.join(" "));
|
|
31
|
+
const response = await fetchWithProxy(config.tokenUrl, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
34
|
+
body: tokenRequestBody.toString()
|
|
35
|
+
});
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
const errorText = await response.text();
|
|
38
|
+
throw new Error(`OAuth token request failed with status ${response.status} ${response.statusText}: ${errorText}`);
|
|
39
|
+
}
|
|
40
|
+
const tokenData = await response.json();
|
|
41
|
+
if (!tokenData.access_token) throw new Error("OAuth token response missing access_token");
|
|
42
|
+
const expiresAt = now + (tokenData.expires_in || 3600) * 1e3;
|
|
43
|
+
logger.debug("[OAuth] Successfully fetched token");
|
|
44
|
+
return {
|
|
45
|
+
accessToken: tokenData.access_token,
|
|
46
|
+
expiresAt
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/providers/mcp/util.ts
|
|
51
|
+
/**
|
|
52
|
+
* Render environment variables in server config auth fields.
|
|
53
|
+
* Supports {{VAR_NAME}} syntax for variable substitution.
|
|
54
|
+
*/
|
|
55
|
+
function renderAuthVars(server, vars) {
|
|
56
|
+
if (!server.auth) return server;
|
|
57
|
+
const renderVars = vars || process.env;
|
|
58
|
+
return {
|
|
59
|
+
...server,
|
|
60
|
+
auth: renderVarsInObject(server.auth, renderVars)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const oauthTokenCache = /* @__PURE__ */ new Map();
|
|
64
|
+
/**
|
|
65
|
+
* Get the cache key for an OAuth config
|
|
66
|
+
*/
|
|
67
|
+
function getOAuthCacheKey(auth) {
|
|
68
|
+
return `${auth.tokenUrl}:${auth.grantType}:${"clientId" in auth ? auth.clientId : ""}:${"username" in auth ? auth.username : ""}`;
|
|
69
|
+
}
|
|
70
|
+
const tokenEndpointCache = /* @__PURE__ */ new Map();
|
|
71
|
+
/**
|
|
72
|
+
* Discover the OAuth token endpoint from the server's well-known metadata.
|
|
73
|
+
* Follows RFC 8414 OAuth 2.0 Authorization Server Metadata.
|
|
74
|
+
* Only requires token_endpoint from the response (unlike SDK which requires authorization_endpoint).
|
|
75
|
+
*/
|
|
76
|
+
async function discoverTokenEndpoint(serverUrl) {
|
|
77
|
+
const cached = tokenEndpointCache.get(serverUrl);
|
|
78
|
+
if (cached) {
|
|
79
|
+
logger.debug(`[MCP Auth] Using cached token endpoint for ${serverUrl}`);
|
|
80
|
+
return cached;
|
|
81
|
+
}
|
|
82
|
+
const url = new URL(serverUrl);
|
|
83
|
+
const baseUrl = `${url.protocol}//${url.host}`;
|
|
84
|
+
const discoveryUrls = [];
|
|
85
|
+
if (url.pathname && url.pathname !== "/") {
|
|
86
|
+
discoveryUrls.push(`${baseUrl}${url.pathname}/.well-known/oauth-authorization-server`);
|
|
87
|
+
discoveryUrls.push(`${baseUrl}/.well-known/oauth-authorization-server${url.pathname}`);
|
|
88
|
+
}
|
|
89
|
+
discoveryUrls.push(`${baseUrl}/.well-known/oauth-authorization-server`);
|
|
90
|
+
for (const discoveryUrl of discoveryUrls) try {
|
|
91
|
+
logger.debug(`[MCP Auth] Trying OAuth discovery at ${discoveryUrl}`);
|
|
92
|
+
const response = await fetchWithProxy(discoveryUrl);
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
logger.debug(`[MCP Auth] Discovery failed at ${discoveryUrl}: ${response.status}`);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const metadata = await response.json();
|
|
98
|
+
if (metadata.token_endpoint) {
|
|
99
|
+
logger.debug(`[MCP Auth] Discovered token endpoint: ${metadata.token_endpoint}`);
|
|
100
|
+
tokenEndpointCache.set(serverUrl, metadata.token_endpoint);
|
|
101
|
+
return metadata.token_endpoint;
|
|
102
|
+
}
|
|
103
|
+
logger.debug(`[MCP Auth] No token_endpoint in metadata from ${discoveryUrl}`);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
logger.debug(`[MCP Auth] Error fetching ${discoveryUrl}: ${error}`);
|
|
106
|
+
}
|
|
107
|
+
throw new Error(`Failed to discover OAuth token endpoint for ${serverUrl}. Please configure tokenUrl explicitly in the auth config.`);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get OAuth token with expiration info, fetching a new one if needed.
|
|
111
|
+
* If tokenUrl is not configured, attempts OAuth discovery to find the token endpoint.
|
|
112
|
+
* Caches tokens and returns cached version if still valid.
|
|
113
|
+
*/
|
|
114
|
+
async function getOAuthTokenWithExpiry(auth, serverUrl) {
|
|
115
|
+
let tokenUrl = auth.tokenUrl;
|
|
116
|
+
if (!tokenUrl) {
|
|
117
|
+
if (!serverUrl) throw new Error("Either tokenUrl or serverUrl is required for OAuth token fetching");
|
|
118
|
+
tokenUrl = await discoverTokenEndpoint(serverUrl);
|
|
119
|
+
}
|
|
120
|
+
const cacheKey = getOAuthCacheKey(auth);
|
|
121
|
+
const cached = oauthTokenCache.get(cacheKey);
|
|
122
|
+
if (cached && Date.now() + 6e4 < cached.expiresAt) {
|
|
123
|
+
logger.debug("[MCP Auth] Using cached OAuth token");
|
|
124
|
+
return {
|
|
125
|
+
accessToken: cached.accessToken,
|
|
126
|
+
expiresAt: cached.expiresAt
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const result = await fetchOAuthToken({
|
|
130
|
+
tokenUrl,
|
|
131
|
+
grantType: auth.grantType,
|
|
132
|
+
clientId: auth.clientId,
|
|
133
|
+
clientSecret: auth.clientSecret,
|
|
134
|
+
username: "username" in auth ? auth.username : void 0,
|
|
135
|
+
password: "password" in auth ? auth.password : void 0,
|
|
136
|
+
scopes: auth.scopes
|
|
137
|
+
});
|
|
138
|
+
oauthTokenCache.set(cacheKey, {
|
|
139
|
+
accessToken: result.accessToken,
|
|
140
|
+
expiresAt: result.expiresAt
|
|
141
|
+
});
|
|
142
|
+
logger.debug("[MCP Auth] Cached OAuth token");
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get OAuth token, fetching a new one if needed.
|
|
147
|
+
* Requires tokenUrl to be configured - throws if not provided.
|
|
148
|
+
*/
|
|
149
|
+
async function getOAuthToken(auth) {
|
|
150
|
+
return (await getOAuthTokenWithExpiry(auth)).accessToken;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get authentication headers for an MCP server configuration.
|
|
154
|
+
* Returns headers for bearer, basic, and api_key (header placement) auth types.
|
|
155
|
+
* For OAuth, use getOAuthToken() first then pass the token.
|
|
156
|
+
* For api_key with query placement, use getAuthQueryParams() instead.
|
|
157
|
+
*/
|
|
158
|
+
function getAuthHeaders(server, oauthToken) {
|
|
159
|
+
if (!server.auth) return {};
|
|
160
|
+
switch (server.auth.type) {
|
|
161
|
+
case "bearer":
|
|
162
|
+
if (!server.auth.token) return {};
|
|
163
|
+
return { Authorization: `Bearer ${server.auth.token}` };
|
|
164
|
+
case "basic": return { Authorization: `Basic ${Buffer.from(`${server.auth.username}:${server.auth.password}`).toString("base64")}` };
|
|
165
|
+
case "api_key": {
|
|
166
|
+
const apiKeyAuth = server.auth;
|
|
167
|
+
const value = apiKeyAuth.value || apiKeyAuth.api_key;
|
|
168
|
+
if (!value) return {};
|
|
169
|
+
if ((apiKeyAuth.placement || "header") === "header") return { [apiKeyAuth.keyName || "X-API-Key"]: value };
|
|
170
|
+
return {};
|
|
171
|
+
}
|
|
172
|
+
case "oauth":
|
|
173
|
+
if (oauthToken) return { Authorization: `Bearer ${oauthToken}` };
|
|
174
|
+
logger.warn("[MCP Auth] OAuth auth configured but no token provided");
|
|
175
|
+
return {};
|
|
176
|
+
default: return {};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get authentication query parameters for api_key auth with query placement.
|
|
181
|
+
* Returns an object with key-value pairs to be added to the URL.
|
|
182
|
+
*/
|
|
183
|
+
function getAuthQueryParams(server) {
|
|
184
|
+
if (!server.auth || server.auth.type !== "api_key") return {};
|
|
185
|
+
const apiKeyAuth = server.auth;
|
|
186
|
+
const value = apiKeyAuth.value || apiKeyAuth.api_key;
|
|
187
|
+
if (!value) return {};
|
|
188
|
+
if ((apiKeyAuth.placement || "header") !== "query") return {};
|
|
189
|
+
return { [apiKeyAuth.keyName || "X-API-Key"]: value };
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Apply query parameters to a URL
|
|
193
|
+
*/
|
|
194
|
+
function applyQueryParams(url, params) {
|
|
195
|
+
if (Object.keys(params).length === 0) return url;
|
|
196
|
+
const urlObj = new URL(url);
|
|
197
|
+
for (const [key, value] of Object.entries(params)) urlObj.searchParams.append(key, value);
|
|
198
|
+
return urlObj.toString();
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Check if auth requires async token fetching (OAuth)
|
|
202
|
+
*/
|
|
203
|
+
function requiresAsyncAuth(server) {
|
|
204
|
+
return server.auth?.type === "oauth";
|
|
205
|
+
}
|
|
206
|
+
//#endregion
|
|
207
|
+
//#region src/util/dataUrl.ts
|
|
208
|
+
/**
|
|
209
|
+
* Check if a string is a data URL
|
|
210
|
+
* @param value String to check
|
|
211
|
+
* @returns true if value is a data URL (starts with "data:")
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* isDataUrl("data:image/jpeg;base64,/9j/...") // true
|
|
215
|
+
* isDataUrl("/9j/4AAQSkZJRg...") // false
|
|
216
|
+
* isDataUrl("https://example.com/image.jpg") // false
|
|
217
|
+
*/
|
|
218
|
+
function isDataUrl(value) {
|
|
219
|
+
return typeof value === "string" && value.startsWith("data:") && value.length > 5;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Parse a data URL into its components
|
|
223
|
+
*
|
|
224
|
+
* Handles data URLs with optional parameters (e.g., charset, name):
|
|
225
|
+
* - `data:image/jpeg;base64,<data>` - Standard format
|
|
226
|
+
* - `data:image/jpeg;charset=utf-8;base64,<data>` - With charset
|
|
227
|
+
* - `data:image/jpeg;name=photo.jpg;base64,<data>` - With filename
|
|
228
|
+
*
|
|
229
|
+
* @param dataUrl Data URL string
|
|
230
|
+
* @returns Parsed components (mimeType and base64Data) or null if invalid
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* parseDataUrl("data:image/jpeg;base64,/9j/...")
|
|
234
|
+
* // { mimeType: "image/jpeg", base64Data: "/9j/..." }
|
|
235
|
+
*
|
|
236
|
+
* parseDataUrl("data:image/jpeg;charset=utf-8;base64,/9j/...")
|
|
237
|
+
* // { mimeType: "image/jpeg", base64Data: "/9j/..." }
|
|
238
|
+
*
|
|
239
|
+
* parseDataUrl("invalid") // null
|
|
240
|
+
*/
|
|
241
|
+
function parseDataUrl(dataUrl) {
|
|
242
|
+
if (!isDataUrl(dataUrl)) return null;
|
|
243
|
+
const match = dataUrl.match(/^data:([^;,]+)(?:;[^,]*)?;base64,(.+)$/);
|
|
244
|
+
if (!match) return null;
|
|
245
|
+
return {
|
|
246
|
+
mimeType: match[1].trim(),
|
|
247
|
+
base64Data: match[2].trim()
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Extract base64 data from a data URL or return original if not a data URL
|
|
252
|
+
* Useful for providers that expect raw base64 (Anthropic, Google)
|
|
253
|
+
*
|
|
254
|
+
* @param value Data URL or raw base64 string
|
|
255
|
+
* @returns Raw base64 string (data URL prefix stripped if present)
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* extractBase64FromDataUrl("data:image/jpeg;base64,/9j/...")
|
|
259
|
+
* // "/9j/..."
|
|
260
|
+
*
|
|
261
|
+
* extractBase64FromDataUrl("/9j/...") // "/9j/..." (unchanged)
|
|
262
|
+
*/
|
|
263
|
+
function extractBase64FromDataUrl(value) {
|
|
264
|
+
const parsed = parseDataUrl(value);
|
|
265
|
+
return parsed ? parsed.base64Data : value;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Build a data URI from a MIME type and base64 data.
|
|
269
|
+
*
|
|
270
|
+
* @param mimeType MIME type (e.g. "image/png")
|
|
271
|
+
* @param base64Data Raw base64-encoded data
|
|
272
|
+
* @returns Data URI string
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* toDataUri("image/png", "iVBORw0KGgo...")
|
|
276
|
+
* // "data:image/png;base64,iVBORw0KGgo..."
|
|
277
|
+
*/
|
|
278
|
+
function toDataUri(mimeType, base64Data) {
|
|
279
|
+
return `data:${mimeType};base64,${base64Data}`;
|
|
280
|
+
}
|
|
281
|
+
//#endregion
|
|
282
|
+
//#region src/providers/google/auth.ts
|
|
283
|
+
/**
|
|
284
|
+
* Centralized authentication manager for Google AI providers.
|
|
285
|
+
*
|
|
286
|
+
* This module handles authentication for both Google AI Studio and Vertex AI,
|
|
287
|
+
* with support for API keys, OAuth/ADC, and service account credentials.
|
|
288
|
+
*
|
|
289
|
+
* Environment variable priority is aligned with the Python SDK:
|
|
290
|
+
* 1. config.apiKey (explicit)
|
|
291
|
+
* 2. VERTEX_API_KEY (Vertex mode only)
|
|
292
|
+
* 3. GOOGLE_API_KEY (primary - Python SDK alignment)
|
|
293
|
+
* 4. GEMINI_API_KEY (secondary)
|
|
294
|
+
*/
|
|
295
|
+
/**
|
|
296
|
+
* Centralized authentication manager for Google AI providers.
|
|
297
|
+
*
|
|
298
|
+
* Handles:
|
|
299
|
+
* - API key resolution with proper priority
|
|
300
|
+
* - OAuth client creation for Vertex AI
|
|
301
|
+
* - Service account credential loading
|
|
302
|
+
* - Conflict detection and warnings
|
|
303
|
+
*/
|
|
304
|
+
var GoogleAuthManager = class {
|
|
305
|
+
static cachedHasDefaultCredentials;
|
|
306
|
+
static pendingHasDefaultCredentials;
|
|
307
|
+
/**
|
|
308
|
+
* Get API key with proper priority order.
|
|
309
|
+
*
|
|
310
|
+
* Priority (aligned with Python SDK):
|
|
311
|
+
* 1. config.apiKey (explicit)
|
|
312
|
+
* 2. VERTEX_API_KEY (Vertex mode only)
|
|
313
|
+
* 3. GOOGLE_API_KEY (primary - Python SDK alignment)
|
|
314
|
+
* 4. GEMINI_API_KEY (secondary)
|
|
315
|
+
* 5. PALM_API_KEY (legacy)
|
|
316
|
+
*
|
|
317
|
+
* @param config - Provider configuration
|
|
318
|
+
* @param env - Environment overrides
|
|
319
|
+
* @param isVertexMode - Whether in Vertex AI mode
|
|
320
|
+
* @returns The resolved API key and its source
|
|
321
|
+
*/
|
|
322
|
+
static getApiKey(config, env, isVertexMode = false) {
|
|
323
|
+
if (config.apiKey) return {
|
|
324
|
+
apiKey: config.apiKey,
|
|
325
|
+
source: "config"
|
|
326
|
+
};
|
|
327
|
+
if (isVertexMode) {
|
|
328
|
+
const vertexKey = env?.VERTEX_API_KEY || getEnvString("VERTEX_API_KEY");
|
|
329
|
+
if (vertexKey) {
|
|
330
|
+
logger.warn("[Google] VERTEX_API_KEY is not a standard SDK env var. Use GOOGLE_API_KEY instead.");
|
|
331
|
+
return {
|
|
332
|
+
apiKey: vertexKey,
|
|
333
|
+
source: "VERTEX_API_KEY"
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const googleKey = env?.GOOGLE_API_KEY || getEnvString("GOOGLE_API_KEY");
|
|
338
|
+
const geminiKey = env?.GEMINI_API_KEY || getEnvString("GEMINI_API_KEY");
|
|
339
|
+
const palmKey = isVertexMode ? void 0 : env?.PALM_API_KEY || getEnvString("PALM_API_KEY");
|
|
340
|
+
if (googleKey && geminiKey) logger.debug("[Google] Both GOOGLE_API_KEY and GEMINI_API_KEY are set. Using GOOGLE_API_KEY.");
|
|
341
|
+
if (googleKey) return {
|
|
342
|
+
apiKey: googleKey,
|
|
343
|
+
source: "GOOGLE_API_KEY"
|
|
344
|
+
};
|
|
345
|
+
if (geminiKey) {
|
|
346
|
+
logger.debug("[Google] GEMINI_API_KEY is not a standard SDK env var. Consider using GOOGLE_API_KEY.");
|
|
347
|
+
return {
|
|
348
|
+
apiKey: geminiKey,
|
|
349
|
+
source: "GEMINI_API_KEY"
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
if (palmKey) {
|
|
353
|
+
logger.warn("[Google] PALM_API_KEY is deprecated. Use GOOGLE_API_KEY instead.");
|
|
354
|
+
return {
|
|
355
|
+
apiKey: palmKey,
|
|
356
|
+
source: "PALM_API_KEY"
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
apiKey: void 0,
|
|
361
|
+
source: "none"
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Validate authentication configuration and emit warnings or throw errors for issues.
|
|
366
|
+
*
|
|
367
|
+
* @param config - Authentication configuration
|
|
368
|
+
* @param env - Environment overrides
|
|
369
|
+
* @throws Error if strictMutualExclusivity is true and mutual exclusivity violation detected
|
|
370
|
+
*/
|
|
371
|
+
static validateAndWarn(config, env) {
|
|
372
|
+
const { apiKey, credentials, projectId, region, vertexai, strictMutualExclusivity } = config;
|
|
373
|
+
const isStrict = strictMutualExclusivity === true;
|
|
374
|
+
const useVertexEnv = getEnvString("GOOGLE_GENAI_USE_VERTEXAI");
|
|
375
|
+
const cloudProject = getEnvString("GOOGLE_CLOUD_PROJECT");
|
|
376
|
+
if ((projectId || region) && apiKey) {
|
|
377
|
+
const message = "[Google] Project/location and API key are mutually exclusive in the client initializer. Use either apiKey for express mode OR projectId/region for OAuth mode, not both.";
|
|
378
|
+
if (isStrict) throw new Error(message);
|
|
379
|
+
else logger.warn(message);
|
|
380
|
+
}
|
|
381
|
+
if (useVertexEnv && vertexai === false) logger.warn("[Google] GOOGLE_GENAI_USE_VERTEXAI is set but vertexai: false was specified in config. Config takes precedence.");
|
|
382
|
+
if (cloudProject && projectId && cloudProject !== projectId) logger.warn("[Google] Both GOOGLE_CLOUD_PROJECT and config.projectId are set with different values. Using config.projectId.");
|
|
383
|
+
if (apiKey && credentials) logger.debug("[Google] Both apiKey and credentials are set. Using API key (express mode). Set expressMode: false to use OAuth/ADC instead.");
|
|
384
|
+
if (vertexai && !apiKey && !projectId && !cloudProject && !credentials) {
|
|
385
|
+
if (!Boolean(env?.GOOGLE_APPLICATION_CREDENTIALS || process.env.GOOGLE_APPLICATION_CREDENTIALS)) logger.debug("[Google] Vertex AI mode enabled but no projectId, credentials, or ADC detected. Authentication may fail.");
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Determine if Vertex AI mode should be used.
|
|
390
|
+
*
|
|
391
|
+
* Priority:
|
|
392
|
+
* 1. Explicit vertexai config flag
|
|
393
|
+
* 2. GOOGLE_GENAI_USE_VERTEXAI env var (Python SDK compatibility)
|
|
394
|
+
* 3. Auto-detect from projectId/credentials presence
|
|
395
|
+
* 4. Default: false (Google AI Studio)
|
|
396
|
+
*
|
|
397
|
+
* @param config - Provider configuration
|
|
398
|
+
* @param env - Environment overrides
|
|
399
|
+
* @returns Whether to use Vertex AI mode
|
|
400
|
+
*/
|
|
401
|
+
static determineVertexMode(config, env) {
|
|
402
|
+
if (config.vertexai !== void 0) return config.vertexai;
|
|
403
|
+
const useVertexEnv = getEnvString("GOOGLE_GENAI_USE_VERTEXAI");
|
|
404
|
+
if (useVertexEnv === "true" || useVertexEnv === "1") {
|
|
405
|
+
logger.debug("[Google] Vertex AI mode enabled via GOOGLE_GENAI_USE_VERTEXAI");
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
if (useVertexEnv === "false" || useVertexEnv === "0") return false;
|
|
409
|
+
const hasProjectId = Boolean(config.projectId || env?.VERTEX_PROJECT_ID || getEnvString("VERTEX_PROJECT_ID") || env?.GOOGLE_PROJECT_ID || getEnvString("GOOGLE_PROJECT_ID") || getEnvString("GOOGLE_CLOUD_PROJECT"));
|
|
410
|
+
const hasCredentials = Boolean(config.credentials);
|
|
411
|
+
if (hasProjectId || hasCredentials) {
|
|
412
|
+
logger.debug("[Google] Auto-detected Vertex AI mode from projectId/credentials. Set vertexai: true/false explicitly to suppress this message.");
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Load credentials from file or return as-is.
|
|
419
|
+
*
|
|
420
|
+
* Supports:
|
|
421
|
+
* - file:// prefix to load from external file
|
|
422
|
+
* - Raw JSON string
|
|
423
|
+
* - Pre-parsed object (from config loading pipeline)
|
|
424
|
+
*
|
|
425
|
+
* @param credentials - Credentials string, file path, or pre-parsed object
|
|
426
|
+
* @returns Processed credentials JSON string
|
|
427
|
+
*/
|
|
428
|
+
static loadCredentials(credentials) {
|
|
429
|
+
if (!credentials) return;
|
|
430
|
+
if (typeof credentials === "object") return JSON.stringify(credentials);
|
|
431
|
+
if (credentials.startsWith("file://")) try {
|
|
432
|
+
const loaded = maybeLoadFromExternalFile(credentials);
|
|
433
|
+
if (typeof loaded === "object") return JSON.stringify(loaded);
|
|
434
|
+
return loaded;
|
|
435
|
+
} catch (error) {
|
|
436
|
+
throw new Error(`Failed to load credentials from file: ${error}`);
|
|
437
|
+
}
|
|
438
|
+
return credentials;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Get or create a Google OAuth client.
|
|
442
|
+
*
|
|
443
|
+
* Supports googleAuthOptions passthrough for advanced configuration
|
|
444
|
+
* like custom scopes, keyFilename, universeDomain, etc.
|
|
445
|
+
*
|
|
446
|
+
* @param options - OAuth client options (can also pass string for backward compatibility)
|
|
447
|
+
* @returns OAuth client and detected project ID
|
|
448
|
+
*/
|
|
449
|
+
static async getOAuthClient(options = {}) {
|
|
450
|
+
const { credentials, googleAuthOptions, scopes, keyFilename } = typeof options === "string" ? { credentials: options } : options;
|
|
451
|
+
const resolvedScopes = scopes ?? googleAuthOptions?.scopes ?? "https://www.googleapis.com/auth/cloud-platform";
|
|
452
|
+
const authOptions = {
|
|
453
|
+
...googleAuthOptions,
|
|
454
|
+
scopes: resolvedScopes
|
|
455
|
+
};
|
|
456
|
+
if (keyFilename && !authOptions.keyFilename) authOptions.keyFilename = keyFilename;
|
|
457
|
+
let GoogleAuthClass;
|
|
458
|
+
try {
|
|
459
|
+
GoogleAuthClass = (await import("google-auth-library")).GoogleAuth;
|
|
460
|
+
} catch {
|
|
461
|
+
throw new Error("The google-auth-library package is required for Vertex AI. Please install it: npm install google-auth-library");
|
|
462
|
+
}
|
|
463
|
+
const auth = new GoogleAuthClass(authOptions);
|
|
464
|
+
const processedCredentials = this.loadCredentials(credentials);
|
|
465
|
+
let client;
|
|
466
|
+
if (processedCredentials) {
|
|
467
|
+
let parsedCredentials;
|
|
468
|
+
try {
|
|
469
|
+
parsedCredentials = JSON.parse(processedCredentials);
|
|
470
|
+
} catch (parseError) {
|
|
471
|
+
const errorMsg = parseError instanceof Error ? parseError.message : String(parseError);
|
|
472
|
+
throw new Error(`[Google] Invalid credentials JSON format: ${errorMsg}`);
|
|
473
|
+
}
|
|
474
|
+
try {
|
|
475
|
+
client = await auth.fromJSON(parsedCredentials);
|
|
476
|
+
} catch (error) {
|
|
477
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
478
|
+
logger.error(`[Google] Could not load credentials: ${errorMsg}`);
|
|
479
|
+
throw new Error(`[Google] Could not load credentials: ${errorMsg}`);
|
|
480
|
+
}
|
|
481
|
+
} else client = await auth.getClient();
|
|
482
|
+
let projectId;
|
|
483
|
+
try {
|
|
484
|
+
projectId = await auth.getProjectId();
|
|
485
|
+
} catch {
|
|
486
|
+
projectId = void 0;
|
|
487
|
+
}
|
|
488
|
+
return {
|
|
489
|
+
client,
|
|
490
|
+
projectId
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Resolve project ID from multiple sources.
|
|
495
|
+
*
|
|
496
|
+
* Priority:
|
|
497
|
+
* 1. config.projectId
|
|
498
|
+
* 2. VERTEX_PROJECT_ID env var
|
|
499
|
+
* 3. GOOGLE_PROJECT_ID env var
|
|
500
|
+
* 4. GOOGLE_CLOUD_PROJECT env var (Python SDK compatibility)
|
|
501
|
+
* 5. Auto-detected from OAuth credentials
|
|
502
|
+
*
|
|
503
|
+
* @param config - Provider configuration
|
|
504
|
+
* @param env - Environment overrides
|
|
505
|
+
* @returns Resolved project ID
|
|
506
|
+
*/
|
|
507
|
+
static async resolveProjectId(config, env) {
|
|
508
|
+
const { projectId: authProjectId } = await this.getOAuthClient({
|
|
509
|
+
credentials: config.credentials,
|
|
510
|
+
googleAuthOptions: config.googleAuthOptions,
|
|
511
|
+
keyFilename: config.keyFilename,
|
|
512
|
+
scopes: config.scopes
|
|
513
|
+
});
|
|
514
|
+
const vertexProjectId = env?.VERTEX_PROJECT_ID || getEnvString("VERTEX_PROJECT_ID");
|
|
515
|
+
const googleProjectId = env?.GOOGLE_PROJECT_ID || getEnvString("GOOGLE_PROJECT_ID");
|
|
516
|
+
const cloudProject = getEnvString("GOOGLE_CLOUD_PROJECT");
|
|
517
|
+
if (vertexProjectId && !config.projectId) logger.debug("[Google] VERTEX_PROJECT_ID is not a standard SDK env var. Consider using GOOGLE_CLOUD_PROJECT.");
|
|
518
|
+
if (googleProjectId && !config.projectId && !vertexProjectId) logger.debug("[Google] GOOGLE_PROJECT_ID is not a standard SDK env var. Consider using GOOGLE_CLOUD_PROJECT.");
|
|
519
|
+
return config.projectId || vertexProjectId || googleProjectId || cloudProject || authProjectId || "";
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Resolve region from multiple sources.
|
|
523
|
+
*
|
|
524
|
+
* Priority:
|
|
525
|
+
* 1. config.region
|
|
526
|
+
* 2. VERTEX_REGION env var
|
|
527
|
+
* 3. GOOGLE_CLOUD_LOCATION env var (Python SDK compatibility)
|
|
528
|
+
* 4. Default: 'global' for Vertex AI without API key (SDK aligned), 'us-central1' otherwise
|
|
529
|
+
*
|
|
530
|
+
* @param config - Provider configuration
|
|
531
|
+
* @param env - Environment overrides
|
|
532
|
+
* @param hasApiKey - Whether an API key is configured (affects default region)
|
|
533
|
+
* @returns Resolved region
|
|
534
|
+
*/
|
|
535
|
+
static resolveRegion(config, env, hasApiKey) {
|
|
536
|
+
const vertexRegion = env?.VERTEX_REGION || getEnvString("VERTEX_REGION");
|
|
537
|
+
const cloudLocation = getEnvString("GOOGLE_CLOUD_LOCATION");
|
|
538
|
+
if (vertexRegion && !config.region) logger.debug("[Google] VERTEX_REGION is not a standard SDK env var. Consider using GOOGLE_CLOUD_LOCATION.");
|
|
539
|
+
const configuredRegion = config.region || vertexRegion || cloudLocation;
|
|
540
|
+
if (configuredRegion) return configuredRegion;
|
|
541
|
+
if (hasApiKey === false) return "global";
|
|
542
|
+
return "us-central1";
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Check if Application Default Credentials are available.
|
|
546
|
+
*
|
|
547
|
+
* @returns True if ADC is available
|
|
548
|
+
*/
|
|
549
|
+
static async hasDefaultCredentials() {
|
|
550
|
+
if (this.cachedHasDefaultCredentials !== void 0) return this.cachedHasDefaultCredentials;
|
|
551
|
+
if (!this.pendingHasDefaultCredentials) {
|
|
552
|
+
const probe = (async () => {
|
|
553
|
+
try {
|
|
554
|
+
await this.getOAuthClient();
|
|
555
|
+
return true;
|
|
556
|
+
} catch {
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
})();
|
|
560
|
+
this.pendingHasDefaultCredentials = probe;
|
|
561
|
+
probe.then((result) => {
|
|
562
|
+
if (this.pendingHasDefaultCredentials === probe) this.cachedHasDefaultCredentials = result;
|
|
563
|
+
return result;
|
|
564
|
+
}).finally(() => {
|
|
565
|
+
if (this.pendingHasDefaultCredentials === probe) this.pendingHasDefaultCredentials = void 0;
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
return this.pendingHasDefaultCredentials;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Clear internal auth detection caches (useful for testing).
|
|
572
|
+
*/
|
|
573
|
+
static clearCache() {
|
|
574
|
+
this.cachedHasDefaultCredentials = void 0;
|
|
575
|
+
this.pendingHasDefaultCredentials = void 0;
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
const loadCredentials = GoogleAuthManager.loadCredentials.bind(GoogleAuthManager);
|
|
579
|
+
const getGoogleClient = GoogleAuthManager.getOAuthClient.bind(GoogleAuthManager);
|
|
580
|
+
const resolveProjectId = GoogleAuthManager.resolveProjectId.bind(GoogleAuthManager);
|
|
581
|
+
const getGoogleApiKey = GoogleAuthManager.getApiKey.bind(GoogleAuthManager);
|
|
582
|
+
const determineGoogleVertexMode = GoogleAuthManager.determineVertexMode.bind(GoogleAuthManager);
|
|
583
|
+
const hasGoogleDefaultCredentials = GoogleAuthManager.hasDefaultCredentials.bind(GoogleAuthManager);
|
|
584
|
+
GoogleAuthManager.clearCache.bind(GoogleAuthManager);
|
|
585
|
+
//#endregion
|
|
586
|
+
//#region src/providers/google/shared.ts
|
|
587
|
+
/**
|
|
588
|
+
* Google AI Studio models with pricing data.
|
|
589
|
+
* Prices are per token (from Google AI pricing page, converted from per-million).
|
|
590
|
+
*
|
|
591
|
+
* Note: Vertex AI may have different pricing for some models.
|
|
592
|
+
*/
|
|
593
|
+
const GOOGLE_MODELS = [
|
|
594
|
+
{
|
|
595
|
+
id: "gemini-3.1-pro-preview",
|
|
596
|
+
cost: {
|
|
597
|
+
input: 2 / 1e6,
|
|
598
|
+
output: 12 / 1e6
|
|
599
|
+
},
|
|
600
|
+
tieredCost: {
|
|
601
|
+
threshold: 2e5,
|
|
602
|
+
above: {
|
|
603
|
+
input: 4 / 1e6,
|
|
604
|
+
output: 18 / 1e6
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
id: "gemini-3-flash-preview",
|
|
610
|
+
cost: {
|
|
611
|
+
input: .5 / 1e6,
|
|
612
|
+
output: 3 / 1e6
|
|
613
|
+
}
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
id: "gemini-3-pro-preview",
|
|
617
|
+
cost: {
|
|
618
|
+
input: 2 / 1e6,
|
|
619
|
+
output: 12 / 1e6
|
|
620
|
+
},
|
|
621
|
+
tieredCost: {
|
|
622
|
+
threshold: 2e5,
|
|
623
|
+
above: {
|
|
624
|
+
input: 4 / 1e6,
|
|
625
|
+
output: 18 / 1e6
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
id: "gemini-2.5-pro",
|
|
631
|
+
cost: {
|
|
632
|
+
input: 1.25 / 1e6,
|
|
633
|
+
output: 10 / 1e6
|
|
634
|
+
},
|
|
635
|
+
tieredCost: {
|
|
636
|
+
threshold: 2e5,
|
|
637
|
+
above: {
|
|
638
|
+
input: 2.5 / 1e6,
|
|
639
|
+
output: 15 / 1e6
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
},
|
|
643
|
+
...[
|
|
644
|
+
"gemini-2.5-pro-preview-05-06",
|
|
645
|
+
"gemini-2.5-pro-preview-06-05",
|
|
646
|
+
"gemini-2.5-computer-use-preview-10-2025"
|
|
647
|
+
].map((id) => ({
|
|
648
|
+
id,
|
|
649
|
+
cost: {
|
|
650
|
+
input: 1.25 / 1e6,
|
|
651
|
+
output: 10 / 1e6
|
|
652
|
+
},
|
|
653
|
+
tieredCost: {
|
|
654
|
+
threshold: 2e5,
|
|
655
|
+
above: {
|
|
656
|
+
input: 2.5 / 1e6,
|
|
657
|
+
output: 15 / 1e6
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
})),
|
|
661
|
+
...[
|
|
662
|
+
"gemini-2.5-flash",
|
|
663
|
+
"gemini-2.5-flash-preview-04-17",
|
|
664
|
+
"gemini-2.5-flash-preview-05-20",
|
|
665
|
+
"gemini-2.5-flash-preview-09-2025"
|
|
666
|
+
].map((id) => ({
|
|
667
|
+
id,
|
|
668
|
+
cost: {
|
|
669
|
+
input: .3 / 1e6,
|
|
670
|
+
output: 2.5 / 1e6
|
|
671
|
+
}
|
|
672
|
+
})),
|
|
673
|
+
...["gemini-2.5-flash-lite", "gemini-2.5-flash-lite-preview-09-2025"].map((id) => ({
|
|
674
|
+
id,
|
|
675
|
+
cost: {
|
|
676
|
+
input: .1 / 1e6,
|
|
677
|
+
output: .4 / 1e6
|
|
678
|
+
}
|
|
679
|
+
})),
|
|
680
|
+
...[
|
|
681
|
+
"gemini-2.0-flash",
|
|
682
|
+
"gemini-2.0-flash-001",
|
|
683
|
+
"gemini-2.0-flash-exp"
|
|
684
|
+
].map((id) => ({
|
|
685
|
+
id,
|
|
686
|
+
cost: {
|
|
687
|
+
input: .1 / 1e6,
|
|
688
|
+
output: .4 / 1e6
|
|
689
|
+
},
|
|
690
|
+
vertexCost: {
|
|
691
|
+
input: .15 / 1e6,
|
|
692
|
+
output: .6 / 1e6
|
|
693
|
+
}
|
|
694
|
+
})),
|
|
695
|
+
...[
|
|
696
|
+
"gemini-2.0-flash-lite",
|
|
697
|
+
"gemini-2.0-flash-lite-001",
|
|
698
|
+
"gemini-2.0-flash-lite-preview-02-05"
|
|
699
|
+
].map((id) => ({
|
|
700
|
+
id,
|
|
701
|
+
cost: {
|
|
702
|
+
input: .075 / 1e6,
|
|
703
|
+
output: .3 / 1e6
|
|
704
|
+
}
|
|
705
|
+
})),
|
|
706
|
+
{
|
|
707
|
+
id: "gemini-2.0-flash-thinking-exp",
|
|
708
|
+
cost: {
|
|
709
|
+
input: .1 / 1e6,
|
|
710
|
+
output: .4 / 1e6
|
|
711
|
+
}
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
id: "gemini-2.0-pro",
|
|
715
|
+
cost: {
|
|
716
|
+
input: 1.25 / 1e6,
|
|
717
|
+
output: 10 / 1e6
|
|
718
|
+
}
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
id: "gemini-1.5-pro",
|
|
722
|
+
cost: {
|
|
723
|
+
input: 1.25 / 1e6,
|
|
724
|
+
output: 5 / 1e6
|
|
725
|
+
},
|
|
726
|
+
tieredCost: {
|
|
727
|
+
threshold: 128e3,
|
|
728
|
+
above: {
|
|
729
|
+
input: 2.5 / 1e6,
|
|
730
|
+
output: 10 / 1e6
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
},
|
|
734
|
+
...[
|
|
735
|
+
"gemini-1.5-pro-001",
|
|
736
|
+
"gemini-1.5-pro-002",
|
|
737
|
+
"gemini-1.5-pro-latest"
|
|
738
|
+
].map((id) => ({
|
|
739
|
+
id,
|
|
740
|
+
cost: {
|
|
741
|
+
input: 1.25 / 1e6,
|
|
742
|
+
output: 5 / 1e6
|
|
743
|
+
},
|
|
744
|
+
tieredCost: {
|
|
745
|
+
threshold: 128e3,
|
|
746
|
+
above: {
|
|
747
|
+
input: 2.5 / 1e6,
|
|
748
|
+
output: 10 / 1e6
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
})),
|
|
752
|
+
...["gemini-1.5-pro-preview-0409", "gemini-1.5-pro-preview-0514"].map((id) => ({
|
|
753
|
+
id,
|
|
754
|
+
cost: {
|
|
755
|
+
input: 1.25 / 1e6,
|
|
756
|
+
output: 5 / 1e6
|
|
757
|
+
},
|
|
758
|
+
tieredCost: {
|
|
759
|
+
threshold: 128e3,
|
|
760
|
+
above: {
|
|
761
|
+
input: 2.5 / 1e6,
|
|
762
|
+
output: 10 / 1e6
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
})),
|
|
766
|
+
...[
|
|
767
|
+
"gemini-1.5-flash",
|
|
768
|
+
"gemini-1.5-flash-001",
|
|
769
|
+
"gemini-1.5-flash-002",
|
|
770
|
+
"gemini-1.5-flash-latest",
|
|
771
|
+
"gemini-1.5-flash-preview-0514"
|
|
772
|
+
].map((id) => ({
|
|
773
|
+
id,
|
|
774
|
+
cost: {
|
|
775
|
+
input: .075 / 1e6,
|
|
776
|
+
output: .3 / 1e6
|
|
777
|
+
},
|
|
778
|
+
tieredCost: {
|
|
779
|
+
threshold: 128e3,
|
|
780
|
+
above: {
|
|
781
|
+
input: .15 / 1e6,
|
|
782
|
+
output: .6 / 1e6
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
})),
|
|
786
|
+
...[
|
|
787
|
+
"gemini-1.5-flash-8b",
|
|
788
|
+
"gemini-1.5-flash-8b-001",
|
|
789
|
+
"gemini-1.5-flash-8b-latest"
|
|
790
|
+
].map((id) => ({
|
|
791
|
+
id,
|
|
792
|
+
cost: {
|
|
793
|
+
input: .0375 / 1e6,
|
|
794
|
+
output: .15 / 1e6
|
|
795
|
+
},
|
|
796
|
+
tieredCost: {
|
|
797
|
+
threshold: 128e3,
|
|
798
|
+
above: {
|
|
799
|
+
input: .075 / 1e6,
|
|
800
|
+
output: .3 / 1e6
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
})),
|
|
804
|
+
...[
|
|
805
|
+
"gemini-1.0-pro",
|
|
806
|
+
"gemini-1.0-pro-001",
|
|
807
|
+
"gemini-1.0-pro-002",
|
|
808
|
+
"gemini-1.0-pro-vision",
|
|
809
|
+
"gemini-1.0-pro-vision-001"
|
|
810
|
+
].map((id) => ({
|
|
811
|
+
id,
|
|
812
|
+
cost: {
|
|
813
|
+
input: .5 / 1e6,
|
|
814
|
+
output: 1.5 / 1e6
|
|
815
|
+
}
|
|
816
|
+
})),
|
|
817
|
+
{
|
|
818
|
+
id: "gemini-pro",
|
|
819
|
+
cost: {
|
|
820
|
+
input: .5 / 1e6,
|
|
821
|
+
output: 1.5 / 1e6
|
|
822
|
+
}
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
id: "gemini-pro-vision",
|
|
826
|
+
cost: {
|
|
827
|
+
input: .5 / 1e6,
|
|
828
|
+
output: 1.5 / 1e6
|
|
829
|
+
}
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
id: "gemini-robotics-er-1.5-preview",
|
|
833
|
+
cost: {
|
|
834
|
+
input: .3 / 1e6,
|
|
835
|
+
output: 2.5 / 1e6
|
|
836
|
+
}
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
id: "gemini-embedding-001",
|
|
840
|
+
cost: {
|
|
841
|
+
input: .15 / 1e6,
|
|
842
|
+
output: 0
|
|
843
|
+
}
|
|
844
|
+
},
|
|
845
|
+
{ id: "aqa" },
|
|
846
|
+
{ id: "chat-bison" },
|
|
847
|
+
{ id: "chat-bison-32k" },
|
|
848
|
+
{ id: "chat-bison-32k@001" },
|
|
849
|
+
{ id: "chat-bison-32k@002" },
|
|
850
|
+
{ id: "chat-bison@001" },
|
|
851
|
+
{ id: "chat-bison@002" },
|
|
852
|
+
{ id: "codechat-bison" },
|
|
853
|
+
{ id: "codechat-bison-32k" },
|
|
854
|
+
{ id: "codechat-bison-32k@001" },
|
|
855
|
+
{ id: "codechat-bison-32k@002" },
|
|
856
|
+
{ id: "codechat-bison@001" },
|
|
857
|
+
{ id: "codechat-bison@002" },
|
|
858
|
+
{ id: "gemini-ultra" },
|
|
859
|
+
{ id: "gemma" },
|
|
860
|
+
{ id: "codegemma" },
|
|
861
|
+
{ id: "paligemma" },
|
|
862
|
+
{ id: "medlm-medium" },
|
|
863
|
+
{ id: "medlm-large" }
|
|
864
|
+
];
|
|
865
|
+
/**
|
|
866
|
+
* List of chat model IDs for backwards compatibility.
|
|
867
|
+
* Used for model validation in ai.studio.ts.
|
|
868
|
+
*/
|
|
869
|
+
const CHAT_MODELS = GOOGLE_MODELS.map((m) => m.id);
|
|
870
|
+
//#endregion
|
|
871
|
+
//#region src/providers/google/types.ts
|
|
872
|
+
const VALID_SCHEMA_TYPES = [
|
|
873
|
+
"TYPE_UNSPECIFIED",
|
|
874
|
+
"STRING",
|
|
875
|
+
"NUMBER",
|
|
876
|
+
"INTEGER",
|
|
877
|
+
"BOOLEAN",
|
|
878
|
+
"ARRAY",
|
|
879
|
+
"OBJECT"
|
|
880
|
+
];
|
|
881
|
+
//#endregion
|
|
882
|
+
//#region src/providers/google/util.ts
|
|
883
|
+
/**
|
|
884
|
+
* Normalizes safety settings to use the correct Google API field name `threshold`.
|
|
885
|
+
* Accepts the legacy `probability` field for backwards compatibility and maps it to `threshold`.
|
|
886
|
+
*/
|
|
887
|
+
function normalizeSafetySettings(safetySettings) {
|
|
888
|
+
if (!safetySettings) return;
|
|
889
|
+
return safetySettings.map(({ category, threshold, probability }) => ({
|
|
890
|
+
category,
|
|
891
|
+
threshold: threshold || probability || ""
|
|
892
|
+
}));
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Calculates the cost for a Google API call.
|
|
896
|
+
*
|
|
897
|
+
* Handles tiered pricing for models where cost varies by prompt size.
|
|
898
|
+
* For example, Gemini Pro models have higher rates for prompts >200k tokens.
|
|
899
|
+
* Some models (e.g. Gemini 2.0 Flash) have different pricing on Vertex AI.
|
|
900
|
+
*
|
|
901
|
+
* @param modelName - The name of the model used
|
|
902
|
+
* @param config - Provider configuration (may contain custom cost override)
|
|
903
|
+
* @param promptTokens - Number of tokens in the prompt
|
|
904
|
+
* @param completionTokens - Number of tokens in the completion
|
|
905
|
+
* @param isVertexMode - Whether the call was made via Vertex AI (uses Vertex pricing when available)
|
|
906
|
+
* @returns The calculated cost in dollars, or undefined if it cannot be calculated
|
|
907
|
+
*/
|
|
908
|
+
function calculateGoogleCost(modelName, config, promptTokens, completionTokens, isVertexMode) {
|
|
909
|
+
const model = GOOGLE_MODELS.find((m) => m.id === modelName);
|
|
910
|
+
if (promptTokens != null && completionTokens != null) {
|
|
911
|
+
if (model?.tieredCost && promptTokens > model.tieredCost.threshold) {
|
|
912
|
+
const inputCost = config.cost ?? model.tieredCost.above.input;
|
|
913
|
+
const outputCost = config.cost ?? model.tieredCost.above.output;
|
|
914
|
+
return inputCost * promptTokens + outputCost * completionTokens;
|
|
915
|
+
}
|
|
916
|
+
if (isVertexMode && model?.vertexCost) {
|
|
917
|
+
const inputCost = config.cost ?? model.vertexCost.input;
|
|
918
|
+
const outputCost = config.cost ?? model.vertexCost.output;
|
|
919
|
+
return inputCost * promptTokens + outputCost * completionTokens;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
return calculateCost(modelName, config, promptTokens, completionTokens, GOOGLE_MODELS);
|
|
923
|
+
}
|
|
924
|
+
const ajv = getAjv();
|
|
925
|
+
ajv.addKeyword("property_ordering");
|
|
926
|
+
const clone = Clone();
|
|
927
|
+
const PartSchema = z.object({
|
|
928
|
+
text: z.string().optional(),
|
|
929
|
+
inline_data: z.object({
|
|
930
|
+
mime_type: z.string(),
|
|
931
|
+
data: z.string()
|
|
932
|
+
}).optional()
|
|
933
|
+
});
|
|
934
|
+
const ContentSchema = z.object({
|
|
935
|
+
role: z.enum(["user", "model"]).optional(),
|
|
936
|
+
parts: z.array(PartSchema)
|
|
937
|
+
});
|
|
938
|
+
const GeminiFormatSchema = z.array(ContentSchema);
|
|
939
|
+
function maybeCoerceToGeminiFormat(contents, options) {
|
|
940
|
+
let coerced = false;
|
|
941
|
+
const parseResult = GeminiFormatSchema.safeParse(contents);
|
|
942
|
+
if (parseResult.success) {
|
|
943
|
+
let systemInst = void 0;
|
|
944
|
+
if (typeof contents === "object" && "system_instruction" in contents) {
|
|
945
|
+
systemInst = contents.system_instruction;
|
|
946
|
+
coerced = true;
|
|
947
|
+
}
|
|
948
|
+
return {
|
|
949
|
+
contents: parseResult.data,
|
|
950
|
+
coerced,
|
|
951
|
+
systemInstruction: systemInst
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
let coercedContents;
|
|
955
|
+
if (typeof contents === "object" && contents !== null && !Array.isArray(contents) && "system_instruction" in contents) {
|
|
956
|
+
const systemInst = contents.system_instruction;
|
|
957
|
+
if ("contents" in contents) coercedContents = contents.contents;
|
|
958
|
+
else coercedContents = [];
|
|
959
|
+
return {
|
|
960
|
+
contents: coercedContents,
|
|
961
|
+
coerced: true,
|
|
962
|
+
systemInstruction: systemInst
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
if (typeof contents === "string") {
|
|
966
|
+
coercedContents = [{ parts: [{ text: contents }] }];
|
|
967
|
+
coerced = true;
|
|
968
|
+
} else if (Array.isArray(contents) && contents.every((item) => typeof item.content === "string")) {
|
|
969
|
+
const targetRole = options?.useAssistantRole ? "assistant" : "model";
|
|
970
|
+
coercedContents = contents.map((item) => ({
|
|
971
|
+
role: item.role === "assistant" ? targetRole : item.role,
|
|
972
|
+
parts: [{ text: item.content }]
|
|
973
|
+
}));
|
|
974
|
+
coerced = true;
|
|
975
|
+
} else if (Array.isArray(contents) && contents.every((item) => item.role && item.content)) {
|
|
976
|
+
const targetRole = options?.useAssistantRole ? "assistant" : "model";
|
|
977
|
+
coercedContents = contents.map((item) => {
|
|
978
|
+
const mappedRole = item.role === "assistant" ? targetRole : item.role;
|
|
979
|
+
if (Array.isArray(item.content)) return {
|
|
980
|
+
role: mappedRole,
|
|
981
|
+
parts: item.content.map((contentItem) => {
|
|
982
|
+
if (typeof contentItem === "string") return { text: contentItem };
|
|
983
|
+
else if (contentItem.type === "text") return { text: contentItem.text };
|
|
984
|
+
else return contentItem;
|
|
985
|
+
})
|
|
986
|
+
};
|
|
987
|
+
else if (typeof item.content === "object") return {
|
|
988
|
+
role: mappedRole,
|
|
989
|
+
parts: [item.content]
|
|
990
|
+
};
|
|
991
|
+
else return {
|
|
992
|
+
role: mappedRole,
|
|
993
|
+
parts: [{ text: item.content }]
|
|
994
|
+
};
|
|
995
|
+
});
|
|
996
|
+
coerced = true;
|
|
997
|
+
} else if (typeof contents === "object" && contents !== null && "parts" in contents) {
|
|
998
|
+
coercedContents = [contents];
|
|
999
|
+
coerced = true;
|
|
1000
|
+
} else {
|
|
1001
|
+
logger.warn(`Unknown format for Gemini: ${JSON.stringify(contents)}`);
|
|
1002
|
+
return {
|
|
1003
|
+
contents: Array.isArray(contents) ? contents : [],
|
|
1004
|
+
coerced: false,
|
|
1005
|
+
systemInstruction: void 0
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
let systemPromptParts = [];
|
|
1009
|
+
coercedContents = coercedContents.filter((message) => {
|
|
1010
|
+
if (message.role === "system" && message.parts.length > 0) {
|
|
1011
|
+
systemPromptParts.push(...message.parts.filter((part) => "text" in part && typeof part.text === "string"));
|
|
1012
|
+
return false;
|
|
1013
|
+
}
|
|
1014
|
+
return true;
|
|
1015
|
+
});
|
|
1016
|
+
if (coercedContents.length === 0 && systemPromptParts.length > 0) {
|
|
1017
|
+
coercedContents = [{
|
|
1018
|
+
role: "user",
|
|
1019
|
+
parts: systemPromptParts
|
|
1020
|
+
}];
|
|
1021
|
+
coerced = true;
|
|
1022
|
+
systemPromptParts = [];
|
|
1023
|
+
}
|
|
1024
|
+
return {
|
|
1025
|
+
contents: coercedContents,
|
|
1026
|
+
coerced,
|
|
1027
|
+
systemInstruction: systemPromptParts.length > 0 ? { parts: systemPromptParts } : void 0
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
let cachedGenerativeLanguageAuth = null;
|
|
1031
|
+
/**
|
|
1032
|
+
* Gets an OAuth2 access token for Google APIs.
|
|
1033
|
+
* Used by providers that need to authenticate via OAuth2 instead of API keys.
|
|
1034
|
+
* @param credentials - Optional credentials JSON string or file:// path
|
|
1035
|
+
* @param scopes - Optional scopes to use. Defaults to cloud-platform + generative-language scopes
|
|
1036
|
+
* @returns The access token string, or undefined if authentication fails
|
|
1037
|
+
*/
|
|
1038
|
+
async function getGoogleAccessToken(credentials) {
|
|
1039
|
+
try {
|
|
1040
|
+
if (!cachedGenerativeLanguageAuth) {
|
|
1041
|
+
let GoogleAuth;
|
|
1042
|
+
try {
|
|
1043
|
+
GoogleAuth = (await import("google-auth-library")).GoogleAuth;
|
|
1044
|
+
cachedGenerativeLanguageAuth = new GoogleAuth({ scopes: [
|
|
1045
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
|
1046
|
+
"https://www.googleapis.com/auth/generative-language.retriever",
|
|
1047
|
+
"https://www.googleapis.com/auth/generative-language.tuning"
|
|
1048
|
+
] });
|
|
1049
|
+
} catch {
|
|
1050
|
+
throw new Error("The google-auth-library package is required as a peer dependency. Please install it in your project or globally.");
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
const processedCredentials = loadCredentials(credentials);
|
|
1054
|
+
let client;
|
|
1055
|
+
if (processedCredentials) client = await cachedGenerativeLanguageAuth.fromJSON(JSON.parse(processedCredentials));
|
|
1056
|
+
else client = await cachedGenerativeLanguageAuth.getClient();
|
|
1057
|
+
return (await client.getAccessToken()).token || void 0;
|
|
1058
|
+
} catch (error) {
|
|
1059
|
+
logger.debug("[GoogleAuth] Could not get access token", { error: error instanceof Error ? error.message : String(error) });
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
function getCandidate(data) {
|
|
1064
|
+
if (!data || !data.candidates || data.candidates.length < 1) {
|
|
1065
|
+
let errorDetails = "No candidates returned in API response.";
|
|
1066
|
+
if (data?.promptFeedback?.blockReason) {
|
|
1067
|
+
errorDetails = `Response blocked: ${data.promptFeedback.blockReason}`;
|
|
1068
|
+
if (data.promptFeedback.safetyRatings) {
|
|
1069
|
+
const flaggedCategories = data.promptFeedback.safetyRatings.filter((rating) => rating.probability !== "NEGLIGIBLE").map((rating) => `${rating.category}: ${rating.probability}`);
|
|
1070
|
+
if (flaggedCategories.length > 0) errorDetails += ` (Safety ratings: ${flaggedCategories.join(", ")})`;
|
|
1071
|
+
}
|
|
1072
|
+
} else if (data?.promptFeedback?.safetyRatings) {
|
|
1073
|
+
const flaggedCategories = data.promptFeedback.safetyRatings.filter((rating) => rating.probability !== "NEGLIGIBLE").map((rating) => `${rating.category}: ${rating.probability}`);
|
|
1074
|
+
if (flaggedCategories.length > 0) errorDetails = `Response may have been blocked due to safety filters: ${flaggedCategories.join(", ")}`;
|
|
1075
|
+
}
|
|
1076
|
+
errorDetails += `\n\nGot response: ${JSON.stringify(data)}`;
|
|
1077
|
+
throw new Error(errorDetails);
|
|
1078
|
+
}
|
|
1079
|
+
if (data.candidates.length > 1) logger.debug(`Expected one candidate in AI Studio API response, but got ${data.candidates.length}: ${JSON.stringify(data)}`);
|
|
1080
|
+
return data.candidates[0];
|
|
1081
|
+
}
|
|
1082
|
+
function formatCandidateContents(candidate) {
|
|
1083
|
+
if (candidate.finishReason && [
|
|
1084
|
+
"SAFETY",
|
|
1085
|
+
"RECITATION",
|
|
1086
|
+
"PROHIBITED_CONTENT",
|
|
1087
|
+
"BLOCKLIST",
|
|
1088
|
+
"SPII"
|
|
1089
|
+
].includes(candidate.finishReason)) {
|
|
1090
|
+
let errorMessage = `Response was blocked with finish reason: ${candidate.finishReason}`;
|
|
1091
|
+
if (candidate.safetyRatings) {
|
|
1092
|
+
const flaggedCategories = candidate.safetyRatings.filter((rating) => rating.probability !== "NEGLIGIBLE" || rating.blocked).map((rating) => `${rating.category}: ${rating.probability}${rating.blocked ? " (BLOCKED)" : ""}`);
|
|
1093
|
+
if (flaggedCategories.length > 0) errorMessage += `\nSafety ratings: ${flaggedCategories.join(", ")}`;
|
|
1094
|
+
}
|
|
1095
|
+
if (candidate.finishReason === "RECITATION") errorMessage += "\n\nThis typically occurs when the response is too similar to content from the model's training data.";
|
|
1096
|
+
else if (candidate.finishReason === "SAFETY") errorMessage += "\n\nThe response was blocked due to safety filters. Consider adjusting safety settings or modifying your prompt.";
|
|
1097
|
+
throw new Error(errorMessage);
|
|
1098
|
+
}
|
|
1099
|
+
if (candidate.content?.parts) {
|
|
1100
|
+
let output = "";
|
|
1101
|
+
let is_text = true;
|
|
1102
|
+
for (const part of candidate.content.parts) if ("text" in part) output += part.text;
|
|
1103
|
+
else is_text = false;
|
|
1104
|
+
if (is_text) return output;
|
|
1105
|
+
else return candidate.content.parts;
|
|
1106
|
+
} else throw new Error(`No output found in response: ${JSON.stringify(candidate)}`);
|
|
1107
|
+
}
|
|
1108
|
+
function mergeParts(parts1, parts2) {
|
|
1109
|
+
if (parts1 === void 0) return parts2;
|
|
1110
|
+
if (typeof parts1 === "string" && typeof parts2 === "string") return parts1 + parts2;
|
|
1111
|
+
const array1 = typeof parts1 === "string" ? [{ text: parts1 }] : parts1;
|
|
1112
|
+
const array2 = typeof parts2 === "string" ? [{ text: parts2 }] : parts2;
|
|
1113
|
+
array1.push(...array2);
|
|
1114
|
+
return array1;
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Normalizes and sanitizes tools configuration for Gemini API compatibility.
|
|
1118
|
+
* - Handles snake_case to camelCase conversion for backwards compatibility
|
|
1119
|
+
* - Sanitizes function declaration schemas to remove unsupported JSON Schema properties
|
|
1120
|
+
* (e.g., additionalProperties, $schema, default) that Gemini doesn't support
|
|
1121
|
+
*/
|
|
1122
|
+
function normalizeTools(tools) {
|
|
1123
|
+
return tools.map((tool) => {
|
|
1124
|
+
const normalizedTool = { ...tool };
|
|
1125
|
+
if (tool.google_search && !normalizedTool.googleSearch) normalizedTool.googleSearch = tool.google_search;
|
|
1126
|
+
if (tool.code_execution && !normalizedTool.codeExecution) normalizedTool.codeExecution = tool.code_execution;
|
|
1127
|
+
if (tool.google_search_retrieval && !normalizedTool.googleSearchRetrieval) normalizedTool.googleSearchRetrieval = tool.google_search_retrieval;
|
|
1128
|
+
if (normalizedTool.functionDeclarations) normalizedTool.functionDeclarations = normalizedTool.functionDeclarations.map((fd) => ({
|
|
1129
|
+
...fd,
|
|
1130
|
+
parameters: fd.parameters ? sanitizeSchemaForGemini(fd.parameters) : void 0
|
|
1131
|
+
}));
|
|
1132
|
+
return normalizedTool;
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
function loadFile(config_var, context_vars) {
|
|
1136
|
+
const fileContents = maybeLoadFromExternalFile(renderVarsInObject(config_var, context_vars));
|
|
1137
|
+
if (typeof fileContents === "string") try {
|
|
1138
|
+
const parsedContents = JSON.parse(fileContents);
|
|
1139
|
+
return Array.isArray(parsedContents) ? normalizeTools(parsedContents) : parsedContents;
|
|
1140
|
+
} catch (err) {
|
|
1141
|
+
logger.debug(`ERROR: failed to convert file contents to JSON:\n${JSON.stringify(err)}`);
|
|
1142
|
+
return fileContents;
|
|
1143
|
+
}
|
|
1144
|
+
if (Array.isArray(fileContents)) return normalizeTools(fileContents);
|
|
1145
|
+
return fileContents;
|
|
1146
|
+
}
|
|
1147
|
+
function isValidBase64Image(data) {
|
|
1148
|
+
const base64Data = isDataUrl(data) ? extractBase64FromDataUrl(data) : data;
|
|
1149
|
+
if (!base64Data || base64Data.length < 20) return false;
|
|
1150
|
+
try {
|
|
1151
|
+
Buffer.from(base64Data, "base64");
|
|
1152
|
+
return base64Data.startsWith("/9j/") || base64Data.startsWith("iVBORw0KGgo") || base64Data.startsWith("R0lGODlh") || base64Data.startsWith("R0lGODdh") || base64Data.startsWith("UklGR") || base64Data.startsWith("Qk0") || base64Data.startsWith("Qk1") || base64Data.startsWith("SUkq") || base64Data.startsWith("TU0A") || base64Data.startsWith("AAABAA");
|
|
1153
|
+
} catch {
|
|
1154
|
+
return false;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
function getMimeTypeFromBase64(base64DataOrUrl) {
|
|
1158
|
+
const parsed = parseDataUrl(base64DataOrUrl);
|
|
1159
|
+
if (parsed) return parsed.mimeType;
|
|
1160
|
+
const base64Data = extractBase64FromDataUrl(base64DataOrUrl);
|
|
1161
|
+
if (base64Data.startsWith("/9j/")) return "image/jpeg";
|
|
1162
|
+
else if (base64Data.startsWith("iVBORw0KGgo")) return "image/png";
|
|
1163
|
+
else if (base64Data.startsWith("R0lGODlh") || base64Data.startsWith("R0lGODdh")) return "image/gif";
|
|
1164
|
+
else if (base64Data.startsWith("UklGR")) return "image/webp";
|
|
1165
|
+
else if (base64Data.startsWith("Qk0") || base64Data.startsWith("Qk1")) return "image/bmp";
|
|
1166
|
+
else if (base64Data.startsWith("SUkq") || base64Data.startsWith("TU0A")) return "image/tiff";
|
|
1167
|
+
else if (base64Data.startsWith("AAABAA")) return "image/x-icon";
|
|
1168
|
+
return "image/jpeg";
|
|
1169
|
+
}
|
|
1170
|
+
function processImagesInContents(contents, contextVars) {
|
|
1171
|
+
if (!contextVars) return contents;
|
|
1172
|
+
if (!Array.isArray(contents)) {
|
|
1173
|
+
logger.warn("[Google] contents is not an array in processImagesInContents", {
|
|
1174
|
+
contentsType: typeof contents,
|
|
1175
|
+
contentsValue: contents
|
|
1176
|
+
});
|
|
1177
|
+
return [];
|
|
1178
|
+
}
|
|
1179
|
+
const base64ToVarName = /* @__PURE__ */ new Map();
|
|
1180
|
+
for (const [varName, value] of Object.entries(contextVars)) if (typeof value === "string" && isValidBase64Image(value)) base64ToVarName.set(value, varName);
|
|
1181
|
+
return contents.map((content) => {
|
|
1182
|
+
if (content.parts) {
|
|
1183
|
+
const newParts = [];
|
|
1184
|
+
for (const part of content.parts) if (part.text) {
|
|
1185
|
+
const lines = part.text.split("\n");
|
|
1186
|
+
let foundValidImage = false;
|
|
1187
|
+
let currentTextBlock = "";
|
|
1188
|
+
const processedParts = [];
|
|
1189
|
+
for (const line of lines) {
|
|
1190
|
+
const trimmedLine = line.trim();
|
|
1191
|
+
if (base64ToVarName.has(trimmedLine) && isValidBase64Image(trimmedLine)) {
|
|
1192
|
+
foundValidImage = true;
|
|
1193
|
+
if (currentTextBlock.length > 0) {
|
|
1194
|
+
processedParts.push({ text: currentTextBlock });
|
|
1195
|
+
currentTextBlock = "";
|
|
1196
|
+
}
|
|
1197
|
+
const mimeType = getMimeTypeFromBase64(trimmedLine);
|
|
1198
|
+
const base64Data = isDataUrl(trimmedLine) ? extractBase64FromDataUrl(trimmedLine) : trimmedLine;
|
|
1199
|
+
processedParts.push({ inlineData: {
|
|
1200
|
+
mimeType,
|
|
1201
|
+
data: base64Data
|
|
1202
|
+
} });
|
|
1203
|
+
} else {
|
|
1204
|
+
if (currentTextBlock.length > 0) currentTextBlock += "\n";
|
|
1205
|
+
currentTextBlock += line;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
if (currentTextBlock.length > 0) processedParts.push({ text: currentTextBlock });
|
|
1209
|
+
if (foundValidImage) newParts.push(...processedParts);
|
|
1210
|
+
else newParts.push(part);
|
|
1211
|
+
} else newParts.push(part);
|
|
1212
|
+
return {
|
|
1213
|
+
...content,
|
|
1214
|
+
parts: newParts
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
return content;
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Parses and processes config-level systemInstruction.
|
|
1222
|
+
* Handles file loading, string-to-Content conversion, and Nunjucks template rendering.
|
|
1223
|
+
*
|
|
1224
|
+
* @param configSystemInstruction - The systemInstruction from config (can be string, Content, or undefined)
|
|
1225
|
+
* @param contextVars - Variables for Nunjucks template rendering
|
|
1226
|
+
* @returns Processed Content object or undefined
|
|
1227
|
+
*/
|
|
1228
|
+
function parseConfigSystemInstruction(configSystemInstruction, contextVars) {
|
|
1229
|
+
if (!configSystemInstruction) return;
|
|
1230
|
+
let configInstruction = clone(configSystemInstruction);
|
|
1231
|
+
if (typeof configSystemInstruction === "string") configInstruction = loadFile(configSystemInstruction, contextVars);
|
|
1232
|
+
if (typeof configInstruction === "string") configInstruction = { parts: [{ text: configInstruction }] };
|
|
1233
|
+
if (contextVars && configInstruction) {
|
|
1234
|
+
const nunjucks = getNunjucksEngine();
|
|
1235
|
+
for (const part of configInstruction.parts) if (part.text) try {
|
|
1236
|
+
part.text = nunjucks.renderString(part.text, contextVars);
|
|
1237
|
+
} catch (err) {
|
|
1238
|
+
throw new Error(`Unable to render nunjucks in systemInstruction: ${err}`);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
return configInstruction;
|
|
1242
|
+
}
|
|
1243
|
+
function geminiFormatAndSystemInstructions(prompt, contextVars, configSystemInstruction, options) {
|
|
1244
|
+
let contents = parseChatPrompt(prompt, [{
|
|
1245
|
+
parts: [{ text: prompt }],
|
|
1246
|
+
role: "user"
|
|
1247
|
+
}]);
|
|
1248
|
+
const { contents: updatedContents, coerced, systemInstruction: parsedSystemInstruction } = maybeCoerceToGeminiFormat(contents, options);
|
|
1249
|
+
if (coerced) {
|
|
1250
|
+
logger.debug(`Coerced JSON prompt to Gemini format: ${JSON.stringify(contents)}`);
|
|
1251
|
+
contents = updatedContents;
|
|
1252
|
+
}
|
|
1253
|
+
let systemInstruction = parsedSystemInstruction;
|
|
1254
|
+
const parsedConfigInstruction = parseConfigSystemInstruction(configSystemInstruction, contextVars);
|
|
1255
|
+
if (parsedConfigInstruction) systemInstruction = systemInstruction ? { parts: [...parsedConfigInstruction.parts, ...systemInstruction.parts] } : parsedConfigInstruction;
|
|
1256
|
+
contents = processImagesInContents(contents, contextVars);
|
|
1257
|
+
return {
|
|
1258
|
+
contents,
|
|
1259
|
+
systemInstruction
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Recursively traverses a JSON schema object and converts
|
|
1264
|
+
* uppercase type keywords (string values) to lowercase.
|
|
1265
|
+
* Handles nested objects and arrays within the schema.
|
|
1266
|
+
* Creates a deep copy to avoid modifying the original schema.
|
|
1267
|
+
*
|
|
1268
|
+
* @param {object | any} schemaNode - The current node (object or value) being processed.
|
|
1269
|
+
* @returns {object | any} - The processed node with type keywords lowercased.
|
|
1270
|
+
*/
|
|
1271
|
+
function normalizeSchemaTypes(schemaNode) {
|
|
1272
|
+
if (typeof schemaNode !== "object" || schemaNode === null) return schemaNode;
|
|
1273
|
+
if (Array.isArray(schemaNode)) return schemaNode.map(normalizeSchemaTypes);
|
|
1274
|
+
const newNode = {};
|
|
1275
|
+
for (const key in schemaNode) if (Object.prototype.hasOwnProperty.call(schemaNode, key)) {
|
|
1276
|
+
const value = schemaNode[key];
|
|
1277
|
+
if (key === "type") if (typeof value === "string" && VALID_SCHEMA_TYPES.includes(value)) newNode[key] = value.toLowerCase();
|
|
1278
|
+
else if (Array.isArray(value)) newNode[key] = value.map((t) => typeof t === "string" && VALID_SCHEMA_TYPES.includes(t) ? t.toLowerCase() : t);
|
|
1279
|
+
else newNode[key] = normalizeSchemaTypes(value);
|
|
1280
|
+
else newNode[key] = normalizeSchemaTypes(value);
|
|
1281
|
+
}
|
|
1282
|
+
return newNode;
|
|
1283
|
+
}
|
|
1284
|
+
function parseStringObject(input) {
|
|
1285
|
+
if (typeof input === "string") return JSON.parse(input);
|
|
1286
|
+
return input;
|
|
1287
|
+
}
|
|
1288
|
+
function validateFunctionCall(output, functions, vars) {
|
|
1289
|
+
let functionCalls;
|
|
1290
|
+
try {
|
|
1291
|
+
let parsedOutput = parseStringObject(output);
|
|
1292
|
+
if ("toolCall" in parsedOutput) {
|
|
1293
|
+
parsedOutput = parsedOutput.toolCall;
|
|
1294
|
+
functionCalls = parsedOutput.functionCalls;
|
|
1295
|
+
} else if (Array.isArray(parsedOutput)) functionCalls = parsedOutput.filter((obj) => Object.prototype.hasOwnProperty.call(obj, "functionCall")).map((obj) => obj.functionCall);
|
|
1296
|
+
else throw new Error("Unrecognized function call format");
|
|
1297
|
+
} catch {
|
|
1298
|
+
throw new Error(`Google did not return a valid-looking function call: ${JSON.stringify(output)}`);
|
|
1299
|
+
}
|
|
1300
|
+
const interpolatedFunctions = loadFile(functions, vars);
|
|
1301
|
+
for (const functionCall of functionCalls) {
|
|
1302
|
+
const functionName = functionCall.name;
|
|
1303
|
+
const functionArgs = parseStringObject(functionCall.args);
|
|
1304
|
+
const functionSchema = (interpolatedFunctions?.find((f) => "functionDeclarations" in f))?.functionDeclarations?.find((f) => f.name === functionName);
|
|
1305
|
+
if (!functionSchema) throw new Error(`Called "${functionName}", but there is no function with that name`);
|
|
1306
|
+
if (Object.keys(functionArgs).length !== 0 && functionSchema?.parameters) {
|
|
1307
|
+
const parameterSchema = normalizeSchemaTypes(functionSchema.parameters);
|
|
1308
|
+
let validate;
|
|
1309
|
+
try {
|
|
1310
|
+
validate = ajv.compile(parameterSchema);
|
|
1311
|
+
} catch (err) {
|
|
1312
|
+
throw new Error(`Tool schema doesn't compile with ajv: ${err}. If this is a valid tool schema you may need to reformulate your assertion without is-valid-function-call.`);
|
|
1313
|
+
}
|
|
1314
|
+
if (!validate(functionArgs)) throw new Error(`Call to "${functionName}":\n${JSON.stringify(functionCall)}\ndoes not match schema:\n${JSON.stringify(validate.errors)}`);
|
|
1315
|
+
} else if (!(JSON.stringify(functionArgs) === "{}" && !functionSchema?.parameters)) throw new Error(`Call to "${functionName}":\n${JSON.stringify(functionCall)}\ndoes not match schema:\n${JSON.stringify(functionSchema)}`);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* Properties supported by Gemini's function calling API.
|
|
1320
|
+
* Based on Google's Schema type definition and API documentation.
|
|
1321
|
+
* @see https://ai.google.dev/api/caching#Schema
|
|
1322
|
+
*/
|
|
1323
|
+
const GEMINI_SUPPORTED_SCHEMA_PROPERTIES = new Set([
|
|
1324
|
+
"type",
|
|
1325
|
+
"format",
|
|
1326
|
+
"description",
|
|
1327
|
+
"nullable",
|
|
1328
|
+
"enum",
|
|
1329
|
+
"maxItems",
|
|
1330
|
+
"minItems",
|
|
1331
|
+
"properties",
|
|
1332
|
+
"required",
|
|
1333
|
+
"propertyOrdering",
|
|
1334
|
+
"items"
|
|
1335
|
+
]);
|
|
1336
|
+
/**
|
|
1337
|
+
* Valid JSON Schema types mapped to Gemini's expected format (uppercase).
|
|
1338
|
+
*/
|
|
1339
|
+
const JSON_SCHEMA_TYPE_MAP = {
|
|
1340
|
+
string: "STRING",
|
|
1341
|
+
number: "NUMBER",
|
|
1342
|
+
integer: "INTEGER",
|
|
1343
|
+
boolean: "BOOLEAN",
|
|
1344
|
+
array: "ARRAY",
|
|
1345
|
+
object: "OBJECT",
|
|
1346
|
+
null: "STRING"
|
|
1347
|
+
};
|
|
1348
|
+
/**
|
|
1349
|
+
* Recursively sanitizes a JSON Schema for Gemini API compatibility.
|
|
1350
|
+
*
|
|
1351
|
+
* - Removes unsupported properties (additionalProperties, $schema, default, title, etc.)
|
|
1352
|
+
* - Converts type values to uppercase (string → STRING, object → OBJECT)
|
|
1353
|
+
* - Recursively processes nested schemas in 'properties' and 'items'
|
|
1354
|
+
*
|
|
1355
|
+
* @param schema - The JSON Schema object to sanitize
|
|
1356
|
+
* @returns A sanitized schema compatible with Gemini's function calling API
|
|
1357
|
+
*/
|
|
1358
|
+
function sanitizeSchemaForGemini(schema) {
|
|
1359
|
+
if (!schema || typeof schema !== "object") return schema;
|
|
1360
|
+
const result = {};
|
|
1361
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
1362
|
+
if (!GEMINI_SUPPORTED_SCHEMA_PROPERTIES.has(key)) continue;
|
|
1363
|
+
if (key === "type") if (typeof value === "string") result[key] = JSON_SCHEMA_TYPE_MAP[value.toLowerCase()] || value.toUpperCase();
|
|
1364
|
+
else result[key] = value;
|
|
1365
|
+
else if (key === "properties" && typeof value === "object" && value !== null) {
|
|
1366
|
+
result[key] = {};
|
|
1367
|
+
for (const [propName, propSchema] of Object.entries(value)) if (typeof propSchema === "object" && propSchema !== null) result[key][propName] = sanitizeSchemaForGemini(propSchema);
|
|
1368
|
+
else result[key][propName] = propSchema;
|
|
1369
|
+
} else if (key === "items" && typeof value === "object" && value !== null) result[key] = sanitizeSchemaForGemini(value);
|
|
1370
|
+
else result[key] = value;
|
|
1371
|
+
}
|
|
1372
|
+
return result;
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Create a cache discriminator from auth headers.
|
|
1376
|
+
*
|
|
1377
|
+
* This is used to ensure different API keys/credentials don't share cached responses.
|
|
1378
|
+
* The discriminator is included as a custom property in fetchWithCache options,
|
|
1379
|
+
* which gets included in the cache key automatically.
|
|
1380
|
+
*
|
|
1381
|
+
* Security note: We hash auth headers rather than using them directly to avoid
|
|
1382
|
+
* exposing sensitive credentials in cache keys or logs. The hash is truncated
|
|
1383
|
+
* to 16 hex characters (64 bits) for brevity - collision probability is acceptably
|
|
1384
|
+
* low for cache key differentiation (birthday problem: ~4 billion entries needed
|
|
1385
|
+
* for 50% collision probability).
|
|
1386
|
+
*
|
|
1387
|
+
* @param headers - Request headers containing auth info
|
|
1388
|
+
* @returns A short hash string for cache key differentiation
|
|
1389
|
+
*/
|
|
1390
|
+
function createAuthCacheDiscriminator(headers) {
|
|
1391
|
+
const authValues = [];
|
|
1392
|
+
for (const name of [
|
|
1393
|
+
"authorization",
|
|
1394
|
+
"x-goog-api-key",
|
|
1395
|
+
"x-api-key",
|
|
1396
|
+
"api-key",
|
|
1397
|
+
"x-goog-user-project"
|
|
1398
|
+
]) {
|
|
1399
|
+
const value = headers[name] || headers[name.toLowerCase()];
|
|
1400
|
+
if (value) authValues.push(`${name}:${value}`);
|
|
1401
|
+
}
|
|
1402
|
+
if (authValues.length === 0) return "";
|
|
1403
|
+
return crypto.createHash("sha256").update(authValues.join("|")).digest("hex").substring(0, 16);
|
|
1404
|
+
}
|
|
1405
|
+
//#endregion
|
|
1406
|
+
//#region src/providers/mcp/transform.ts
|
|
1407
|
+
function transformMCPToolsToOpenAi(tools) {
|
|
1408
|
+
return tools.map((tool) => {
|
|
1409
|
+
const schema = tool.inputSchema;
|
|
1410
|
+
let properties = {};
|
|
1411
|
+
let required = void 0;
|
|
1412
|
+
let additionalProperties = void 0;
|
|
1413
|
+
if (schema && typeof schema === "object" && "properties" in schema) {
|
|
1414
|
+
properties = schema.properties ?? {};
|
|
1415
|
+
required = schema.required;
|
|
1416
|
+
if ("additionalProperties" in schema) additionalProperties = schema.additionalProperties;
|
|
1417
|
+
} else if (schema && typeof schema === "object") properties = {};
|
|
1418
|
+
else properties = {};
|
|
1419
|
+
return {
|
|
1420
|
+
type: "function",
|
|
1421
|
+
function: {
|
|
1422
|
+
name: tool.name,
|
|
1423
|
+
description: tool.description,
|
|
1424
|
+
parameters: {
|
|
1425
|
+
type: "object",
|
|
1426
|
+
properties,
|
|
1427
|
+
...required && required.length > 0 ? { required } : {},
|
|
1428
|
+
...additionalProperties === void 0 ? {} : { additionalProperties }
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
};
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
function transformMCPToolsToAnthropic(tools) {
|
|
1435
|
+
return tools.map((tool) => {
|
|
1436
|
+
const { $schema: _$schema, ...cleanSchema } = tool.inputSchema;
|
|
1437
|
+
return {
|
|
1438
|
+
name: tool.name,
|
|
1439
|
+
description: tool.description,
|
|
1440
|
+
input_schema: {
|
|
1441
|
+
type: "object",
|
|
1442
|
+
...cleanSchema
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
function transformMCPToolsToGoogle(tools) {
|
|
1448
|
+
return [{ functionDeclarations: tools.map((tool) => {
|
|
1449
|
+
const schema = tool.inputSchema;
|
|
1450
|
+
let parameters;
|
|
1451
|
+
if (schema && typeof schema === "object") {
|
|
1452
|
+
parameters = sanitizeSchemaForGemini(schema);
|
|
1453
|
+
if (!parameters.type) parameters.type = "OBJECT";
|
|
1454
|
+
if (!parameters.properties) parameters.properties = {};
|
|
1455
|
+
} else parameters = {
|
|
1456
|
+
type: "OBJECT",
|
|
1457
|
+
properties: {}
|
|
1458
|
+
};
|
|
1459
|
+
return {
|
|
1460
|
+
name: tool.name,
|
|
1461
|
+
description: tool.description,
|
|
1462
|
+
parameters
|
|
1463
|
+
};
|
|
1464
|
+
}) }];
|
|
1465
|
+
}
|
|
1466
|
+
async function transformMCPConfigToClaudeCode(config) {
|
|
1467
|
+
const serverConfigs = config.servers ?? [];
|
|
1468
|
+
if (config.server) serverConfigs.push(config.server);
|
|
1469
|
+
return (await Promise.all(serverConfigs.map((server) => transformMCPServerConfigToClaudeCode(server)))).reduce((acc, transformed) => {
|
|
1470
|
+
const [key, out] = transformed;
|
|
1471
|
+
acc[key] = out;
|
|
1472
|
+
return acc;
|
|
1473
|
+
}, {});
|
|
1474
|
+
}
|
|
1475
|
+
async function transformMCPServerConfigToClaudeCode(config) {
|
|
1476
|
+
const key = config.name ?? config.url ?? config.command ?? "default";
|
|
1477
|
+
let out;
|
|
1478
|
+
if (config.url) {
|
|
1479
|
+
const renderedConfig = renderAuthVars(config);
|
|
1480
|
+
let oauthToken;
|
|
1481
|
+
if (requiresAsyncAuth(renderedConfig) && renderedConfig.auth?.type === "oauth") oauthToken = await getOAuthToken(renderedConfig.auth);
|
|
1482
|
+
const queryParams = getAuthQueryParams(renderedConfig);
|
|
1483
|
+
out = {
|
|
1484
|
+
type: "http",
|
|
1485
|
+
url: applyQueryParams(config.url, queryParams),
|
|
1486
|
+
headers: {
|
|
1487
|
+
...config.headers ?? {},
|
|
1488
|
+
...getAuthHeaders(renderedConfig, oauthToken)
|
|
1489
|
+
}
|
|
1490
|
+
};
|
|
1491
|
+
} else if (config.command && config.args) out = {
|
|
1492
|
+
type: "stdio",
|
|
1493
|
+
command: config.command,
|
|
1494
|
+
args: config.args
|
|
1495
|
+
};
|
|
1496
|
+
else if (config.path) out = {
|
|
1497
|
+
type: "stdio",
|
|
1498
|
+
command: config.path.endsWith(".py") ? process.platform === "win32" ? "python" : "python3" : process.execPath,
|
|
1499
|
+
args: [config.path]
|
|
1500
|
+
};
|
|
1501
|
+
else throw new Error("MCP configuration cannot be converted to Claude Agent SDK MCP server config");
|
|
1502
|
+
return [key, out];
|
|
1503
|
+
}
|
|
1504
|
+
//#endregion
|
|
1505
|
+
export { TOKEN_REFRESH_BUFFER_MS as A, parseDataUrl as C, getAuthQueryParams as D, getAuthHeaders as E, getOAuthTokenWithExpiry as O, resolveProjectId as S, applyQueryParams as T, determineGoogleVertexMode as _, calculateGoogleCost as a, hasGoogleDefaultCredentials as b, geminiFormatAndSystemInstructions as c, mergeParts as d, normalizeSafetySettings as f, GoogleAuthManager as g, CHAT_MODELS as h, transformMCPToolsToOpenAi as i, renderAuthVars as k, getCandidate as l, validateFunctionCall as m, transformMCPToolsToAnthropic as n, createAuthCacheDiscriminator as o, normalizeTools as p, transformMCPToolsToGoogle as r, formatCandidateContents as s, transformMCPConfigToClaudeCode as t, getGoogleAccessToken as u, getGoogleApiKey as v, toDataUri as w, loadCredentials as x, getGoogleClient as y };
|
|
1506
|
+
|
|
1507
|
+
//# sourceMappingURL=transform-CY1wbpRy.js.map
|