* Inject hosted API key if tool supports it and user didn't provide one. * Checks BYOK workspace keys first, then uses the HostedKeyRateLimiter for round-robin key selection. * Returns whether a hosted (billable) key was injected and which env var it came from.
( tool: ToolConfig, params: Record<string, unknown>, executionContext: ExecutionContext | undefined, requestId: string )
| 224 | * Returns whether a hosted (billable) key was injected and which env var it came from. |
| 225 | */ |
| 226 | async function injectHostedKeyIfNeeded( |
| 227 | tool: ToolConfig, |
| 228 | params: Record<string, unknown>, |
| 229 | executionContext: ExecutionContext | undefined, |
| 230 | requestId: string |
| 231 | ): Promise<HostedKeyInjectionResult> { |
| 232 | if (!tool.hosting) return { isUsingHostedKey: false } |
| 233 | if (!isHosted) return { isUsingHostedKey: false } |
| 234 | if (tool.hosting.enabled && !tool.hosting.enabled(params)) { |
| 235 | return { isUsingHostedKey: false } |
| 236 | } |
| 237 | |
| 238 | const { envKeyPrefix, apiKeyParam, byokProviderId, rateLimit } = tool.hosting |
| 239 | const userProvidedKey = params[apiKeyParam] |
| 240 | if (typeof userProvidedKey === 'string' && userProvidedKey.trim().length > 0) { |
| 241 | return { isUsingHostedKey: false } |
| 242 | } |
| 243 | |
| 244 | const { workspaceId, userId, workflowId } = resolveToolScope(params, executionContext) |
| 245 | |
| 246 | // Check BYOK workspace key first |
| 247 | if (byokProviderId && workspaceId) { |
| 248 | try { |
| 249 | const byokResult = await getBYOKKey(workspaceId, byokProviderId as BYOKProviderId) |
| 250 | if (byokResult) { |
| 251 | params[apiKeyParam] = byokResult.apiKey |
| 252 | logger.info(`[${requestId}] Using BYOK key for ${tool.id}`) |
| 253 | return { isUsingHostedKey: false } // Don't bill - user's own key |
| 254 | } |
| 255 | } catch (error) { |
| 256 | logger.error(`[${requestId}] Failed to get BYOK key for ${tool.id}:`, error) |
| 257 | // Fall through to hosted key |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | const rateLimiter = getHostedKeyRateLimiter() |
| 262 | const provider = byokProviderId || tool.id |
| 263 | const billingActorId = workspaceId |
| 264 | |
| 265 | if (!billingActorId) { |
| 266 | logger.error(`[${requestId}] No workspace ID available for hosted key rate limiting`) |
| 267 | return { isUsingHostedKey: false } |
| 268 | } |
| 269 | |
| 270 | const acquireResult = await rateLimiter.acquireKey( |
| 271 | provider, |
| 272 | envKeyPrefix, |
| 273 | rateLimit, |
| 274 | billingActorId, |
| 275 | executionContext?.abortSignal |
| 276 | ) |
| 277 | |
| 278 | if (!acquireResult.success && acquireResult.billingActorRateLimited) { |
| 279 | logger.warn(`[${requestId}] Billing actor ${billingActorId} rate limited for ${tool.id}`, { |
| 280 | provider, |
| 281 | retryAfterMs: acquireResult.retryAfterMs, |
| 282 | }) |
| 283 |
no test coverage detected