@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.
Files changed (199) hide show
  1. package/LICENSE +19 -0
  2. package/dist/src/accounts-Bt1oJb1Z.cjs +219 -0
  3. package/dist/src/accounts-DjOU8Rm3.js +178 -0
  4. package/dist/src/agentic-utils-D03IiXQc.js +153 -0
  5. package/dist/src/agentic-utils-Dh7xaMQM.cjs +180 -0
  6. package/dist/src/agents-C6BIMlZa.js +231 -0
  7. package/dist/src/agents-DvIpNX1L.cjs +666 -0
  8. package/dist/src/agents-ZP0RP9vV.cjs +231 -0
  9. package/dist/src/agents-maJXdjbR.js +665 -0
  10. package/dist/src/aimlapi-BTbQjG2E.cjs +30 -0
  11. package/dist/src/aimlapi-CwMxqfXP.js +30 -0
  12. package/dist/src/audio-BBUdvsde.cjs +97 -0
  13. package/dist/src/audio-D5DPZ7I-.js +97 -0
  14. package/dist/src/base-BEysXrkq.cjs +222 -0
  15. package/dist/src/base-C451JQfq.js +193 -0
  16. package/dist/src/blobs-BY8MDmpo.js +230 -0
  17. package/dist/src/blobs-BgcNn97m.cjs +256 -0
  18. package/dist/src/cache-BBE_lsTA.cjs +4 -0
  19. package/dist/src/cache-BkrqU5Ba.js +237 -0
  20. package/dist/src/cache-DsCxFlsZ.cjs +297 -0
  21. package/dist/src/chat-CPJWDP6a.cjs +289 -0
  22. package/dist/src/chat-CXX3xzkk.cjs +811 -0
  23. package/dist/src/chat-CcDgZFJ4.js +787 -0
  24. package/dist/src/chat-Dz5ZeGO2.js +289 -0
  25. package/dist/src/chatkit-Dw0mKkML.cjs +1158 -0
  26. package/dist/src/chatkit-swAIVuea.js +1157 -0
  27. package/dist/src/chunk-DEq-mXcV.js +15 -0
  28. package/dist/src/claude-agent-sdk-BXZJtOg6.js +379 -0
  29. package/dist/src/claude-agent-sdk-CkfyjDoG.cjs +383 -0
  30. package/dist/src/cloudflare-ai-BzpJcqUH.js +161 -0
  31. package/dist/src/cloudflare-ai-Cmy_R1y2.cjs +161 -0
  32. package/dist/src/cloudflare-gateway-B9tVQKok.cjs +272 -0
  33. package/dist/src/cloudflare-gateway-DrD3ew3H.js +272 -0
  34. package/dist/src/codex-sdk-Dezj9Nwm.js +1056 -0
  35. package/dist/src/codex-sdk-Dl9D4k5B.cjs +1060 -0
  36. package/dist/src/cometapi-C-9YvCHC.js +54 -0
  37. package/dist/src/cometapi-DHgDKoO2.cjs +54 -0
  38. package/dist/src/completion-B8Ctyxpr.js +120 -0
  39. package/dist/src/completion-Cxrt08sj.cjs +131 -0
  40. package/dist/src/createHash-BwgE13yv.cjs +27 -0
  41. package/dist/src/createHash-DmPQkvBh.js +15 -0
  42. package/dist/src/docker-BiqcTwLv.js +80 -0
  43. package/dist/src/docker-C7tEJnP-.cjs +80 -0
  44. package/dist/src/esm-C62Zofr1.cjs +409 -0
  45. package/dist/src/esm-DMVc93eh.js +379 -0
  46. package/dist/src/evalResult-C3NJPQOo.cjs +301 -0
  47. package/dist/src/evalResult-C7JJAPBb.js +295 -0
  48. package/dist/src/evalResult-DoVTZZWI.cjs +2 -0
  49. package/dist/src/extractor-DnMD3fwt.cjs +391 -0
  50. package/dist/src/extractor-DtlL28vL.js +374 -0
  51. package/dist/src/fetch-BTxakTSg.cjs +1133 -0
  52. package/dist/src/fetch-DQckpUFz.js +928 -0
  53. package/dist/src/fileExtensions-DnqA1y9x.js +85 -0
  54. package/dist/src/fileExtensions-bYh77CN8.cjs +114 -0
  55. package/dist/src/genaiTracer-CyZrmaK0.cjs +268 -0
  56. package/dist/src/genaiTracer-D3fD9dNV.js +256 -0
  57. package/dist/src/graders-BNscxFrU.js +13644 -0
  58. package/dist/src/graders-D2oE9Msq.js +2 -0
  59. package/dist/src/graders-c0Ez_w-9.cjs +2 -0
  60. package/dist/src/graders-d0F2M3e9.cjs +14056 -0
  61. package/dist/src/image-0ZhE0VlR.cjs +280 -0
  62. package/dist/src/image-CWE1pdNv.js +257 -0
  63. package/dist/src/image-D9ZK6hwL.js +163 -0
  64. package/dist/src/image-DKZgZITg.cjs +163 -0
  65. package/dist/src/index.cjs +11366 -0
  66. package/dist/src/index.d.cts +19640 -0
  67. package/dist/src/index.d.ts +19641 -0
  68. package/dist/src/index.js +11306 -0
  69. package/dist/src/invariant-Ddh24eXh.js +25 -0
  70. package/dist/src/invariant-kfQ8Bu82.cjs +30 -0
  71. package/dist/src/knowledgeBase-BgPyGFUd.cjs +122 -0
  72. package/dist/src/knowledgeBase-DyHilYaP.js +122 -0
  73. package/dist/src/litellm-CyMeneHS.js +135 -0
  74. package/dist/src/litellm-DWDF73yF.cjs +135 -0
  75. package/dist/src/logger-C40ZGil9.js +717 -0
  76. package/dist/src/logger-DyfK9PBt.cjs +917 -0
  77. package/dist/src/luma-ray-BAU9X_ep.cjs +315 -0
  78. package/dist/src/luma-ray-nwVseBbv.js +313 -0
  79. package/dist/src/messages-B5ADWTTv.js +245 -0
  80. package/dist/src/messages-BCnZfqrS.cjs +257 -0
  81. package/dist/src/meteor-DLZZ3osF.cjs +134 -0
  82. package/dist/src/meteor-DUiCJRC-.js +134 -0
  83. package/dist/src/modelslab-00cveB8L.cjs +163 -0
  84. package/dist/src/modelslab-D9sCU_L7.js +163 -0
  85. package/dist/src/nova-reel-CTapvqYH.js +276 -0
  86. package/dist/src/nova-reel-DlWuuroF.cjs +278 -0
  87. package/dist/src/nova-sonic-5UPWfeMv.cjs +363 -0
  88. package/dist/src/nova-sonic-BhSwQNym.js +363 -0
  89. package/dist/src/openai-BWrJK9d8.cjs +52 -0
  90. package/dist/src/openai-DumO8WQn.js +47 -0
  91. package/dist/src/openclaw-B8brrjC_.cjs +577 -0
  92. package/dist/src/openclaw-Bkayww9q.js +571 -0
  93. package/dist/src/opencode-sdk-7xjoDNiM.cjs +562 -0
  94. package/dist/src/opencode-sdk-SGwAPxht.js +558 -0
  95. package/dist/src/otlpReceiver-CoAHfAN9.cjs +15 -0
  96. package/dist/src/otlpReceiver-oO3EQwI9.js +14 -0
  97. package/dist/src/providerRegistry-4yjhaEM8.js +45 -0
  98. package/dist/src/providerRegistry-DhV4rJIc.cjs +50 -0
  99. package/dist/src/providers-B5RJVG-7.cjs +33609 -0
  100. package/dist/src/providers-BdmZCLzV.js +33262 -0
  101. package/dist/src/providers-CxtRxn8e.js +2 -0
  102. package/dist/src/providers-DnQLNbx1.cjs +3 -0
  103. package/dist/src/pythonUtils-BD0druiM.cjs +275 -0
  104. package/dist/src/pythonUtils-IBhn5YGR.js +249 -0
  105. package/dist/src/quiverai-BDOwZBsM.cjs +213 -0
  106. package/dist/src/quiverai-D3JTF5lD.js +213 -0
  107. package/dist/src/responses-B2LCDCXZ.js +667 -0
  108. package/dist/src/responses-BvNm4Xv9.cjs +685 -0
  109. package/dist/src/rubyUtils-B0NwnfpY.cjs +245 -0
  110. package/dist/src/rubyUtils-BroxzZ7c.cjs +2 -0
  111. package/dist/src/rubyUtils-hqVw5UvJ.js +222 -0
  112. package/dist/src/sagemaker-Cno2V-Sx.js +689 -0
  113. package/dist/src/sagemaker-fV_KUgs5.cjs +691 -0
  114. package/dist/src/server-BOuAXb06.cjs +238 -0
  115. package/dist/src/server-CtI-EWzm.cjs +2 -0
  116. package/dist/src/server-Cy3DZymt.js +189 -0
  117. package/dist/src/slack-CP8xBePa.js +135 -0
  118. package/dist/src/slack-DSQ1yXVb.cjs +135 -0
  119. package/dist/src/store-BwDDaBjb.cjs +246 -0
  120. package/dist/src/store-DcbLC593.cjs +2 -0
  121. package/dist/src/store-IGpqMIkv.js +240 -0
  122. package/dist/src/tables-3Q2cL7So.cjs +373 -0
  123. package/dist/src/tables-Bi2fjr4W.js +288 -0
  124. package/dist/src/telemetry-Bg2WqF79.js +161 -0
  125. package/dist/src/telemetry-D0x6u5kX.cjs +166 -0
  126. package/dist/src/telemetry-DXNimrI0.cjs +2 -0
  127. package/dist/src/text-B_UCRPp2.js +22 -0
  128. package/dist/src/text-CW1cyrwj.cjs +33 -0
  129. package/dist/src/tokenUsageUtils-NYT-WKS6.js +138 -0
  130. package/dist/src/tokenUsageUtils-bVa1ga6f.cjs +173 -0
  131. package/dist/src/transcription-Cl_W16Pr.js +122 -0
  132. package/dist/src/transcription-yt1EecY8.cjs +124 -0
  133. package/dist/src/transform-BCtGrl_W.cjs +228 -0
  134. package/dist/src/transform-Bv6gG2MJ.cjs +1688 -0
  135. package/dist/src/transform-CY1wbpRy.js +1507 -0
  136. package/dist/src/transform-DU8rUL9P.cjs +2 -0
  137. package/dist/src/transform-yWaShiKr.js +216 -0
  138. package/dist/src/transformersAvailability-BGkzavwb.js +35 -0
  139. package/dist/src/transformersAvailability-DKoRtQLy.cjs +35 -0
  140. package/dist/src/types-5aqHpBwE.cjs +3769 -0
  141. package/dist/src/types-Bn6D9c4U.js +3300 -0
  142. package/dist/src/util-BkKlTkI2.js +293 -0
  143. package/dist/src/util-CTh0bfOm.cjs +1119 -0
  144. package/dist/src/util-D17oBwo7.cjs +328 -0
  145. package/dist/src/util-DsS_-v4p.js +613 -0
  146. package/dist/src/util-DuntT1Ga.js +951 -0
  147. package/dist/src/util-aWjdCYMI.cjs +667 -0
  148. package/dist/src/utils-CisQwpjA.js +94 -0
  149. package/dist/src/utils-yWamDvmz.cjs +123 -0
  150. package/dist/tsconfig.tsbuildinfo +1 -0
  151. package/drizzle/0000_lush_hellion.sql +36 -0
  152. package/drizzle/0001_wide_calypso.sql +3 -0
  153. package/drizzle/0002_tidy_juggernaut.sql +1 -0
  154. package/drizzle/0003_lively_naoko.sql +8 -0
  155. package/drizzle/0004_minor_peter_quill.sql +19 -0
  156. package/drizzle/0005_silky_millenium_guard.sql +2 -0
  157. package/drizzle/0006_harsh_caretaker.sql +42 -0
  158. package/drizzle/0007_cloudy_wong.sql +1 -0
  159. package/drizzle/0008_broad_boomer.sql +2 -0
  160. package/drizzle/0009_strong_marten_broadcloak.sql +19 -0
  161. package/drizzle/0010_needy_bishop.sql +11 -0
  162. package/drizzle/0011_moaning_millenium_guard.sql +1 -0
  163. package/drizzle/0012_late_marten_broadcloak.sql +2 -0
  164. package/drizzle/0013_previous_dormammu.sql +9 -0
  165. package/drizzle/0014_lazy_captain_universe.sql +2 -0
  166. package/drizzle/0015_zippy_wallop.sql +29 -0
  167. package/drizzle/0016_jazzy_zemo.sql +2 -0
  168. package/drizzle/0017_reflective_praxagora.sql +4 -0
  169. package/drizzle/0018_fat_vanisher.sql +22 -0
  170. package/drizzle/0019_new_clint_barton.sql +8 -0
  171. package/drizzle/0020_skinny_maverick.sql +1 -0
  172. package/drizzle/0021_mysterious_madelyne_pryor.sql +13 -0
  173. package/drizzle/0022_sleepy_ultimo.sql +25 -0
  174. package/drizzle/0023_wooden_mandrill.sql +2 -0
  175. package/drizzle/AGENTS.md +68 -0
  176. package/drizzle/CLAUDE.md +1 -0
  177. package/drizzle/meta/0000_snapshot.json +221 -0
  178. package/drizzle/meta/0001_snapshot.json +214 -0
  179. package/drizzle/meta/0002_snapshot.json +221 -0
  180. package/drizzle/meta/0005_snapshot.json +369 -0
  181. package/drizzle/meta/0006_snapshot.json +638 -0
  182. package/drizzle/meta/0007_snapshot.json +640 -0
  183. package/drizzle/meta/0008_snapshot.json +649 -0
  184. package/drizzle/meta/0009_snapshot.json +554 -0
  185. package/drizzle/meta/0010_snapshot.json +619 -0
  186. package/drizzle/meta/0011_snapshot.json +627 -0
  187. package/drizzle/meta/0012_snapshot.json +639 -0
  188. package/drizzle/meta/0013_snapshot.json +717 -0
  189. package/drizzle/meta/0014_snapshot.json +717 -0
  190. package/drizzle/meta/0015_snapshot.json +897 -0
  191. package/drizzle/meta/0016_snapshot.json +1031 -0
  192. package/drizzle/meta/0018_snapshot.json +1210 -0
  193. package/drizzle/meta/0019_snapshot.json +1165 -0
  194. package/drizzle/meta/0020_snapshot.json +1232 -0
  195. package/drizzle/meta/0021_snapshot.json +1311 -0
  196. package/drizzle/meta/0022_snapshot.json +1481 -0
  197. package/drizzle/meta/0023_snapshot.json +1496 -0
  198. package/drizzle/meta/_journal.json +174 -0
  199. 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