fix(api-key-reminder) Add reminder on hosted keys that api key isnt needed (#3512)

* Add reminder on hosted keys that api key isnt needed

* Fix test case

---------

Co-authored-by: Theodore Li <theo@sim.ai>
This commit is contained in:
Theodore Li
2026-03-10 22:34:29 -04:00
committed by GitHub
co-authored by Theodore Li
parent 5815d9f556
commit 7151e81fff
5 changed files with 87 additions and 5 deletions
+5 -1
View File
@@ -1,5 +1,6 @@
import { createLogger } from '@sim/logger'
import { processFileAttachments } from '@/lib/copilot/chat-context'
import { getCopilotToolDescription } from '@/lib/copilot/tool-descriptions'
import { isHosted } from '@/lib/core/config/feature-flags'
import { createMcpToolId } from '@/lib/mcp/utils'
import { getWorkflowById } from '@/lib/workflows/utils'
@@ -55,7 +56,10 @@ export async function buildIntegrationToolSchemas(): Promise<ToolSchema[]> {
const strippedName = stripVersionSuffix(toolId)
integrationTools.push({
name: strippedName,
description: toolConfig.description || toolConfig.name || strippedName,
description: getCopilotToolDescription(toolConfig, {
isHosted,
fallbackName: strippedName,
}),
input_schema: userSchema as unknown as Record<string, unknown>,
defer_loading: true,
...(toolConfig.oauth?.required && {
@@ -0,0 +1,43 @@
import { describe, expect, it } from 'vitest'
import { getCopilotToolDescription } from '@/lib/copilot/tool-descriptions'
describe('getCopilotToolDescription', () => {
it.concurrent('returns the base description when hosted keys are not active', () => {
expect(
getCopilotToolDescription(
{
name: 'Brandfetch Search',
description: 'Search for brands by company name',
hosting: { apiKeyParam: 'apiKey' } as never,
},
{ isHosted: false }
)
).toBe('Search for brands by company name')
})
it.concurrent('appends the hosted API key note when the tool supports hosting', () => {
expect(
getCopilotToolDescription(
{
name: 'Brandfetch Search',
description: 'Search for brands by company name',
hosting: { apiKeyParam: 'apiKey' } as never,
},
{ isHosted: true }
)
).toBe('Search for brands by company name <note>API key is hosted by Sim.</note>')
})
it.concurrent('uses the fallback name when no description exists', () => {
expect(
getCopilotToolDescription(
{
name: '',
description: '',
hosting: { apiKeyParam: 'apiKey' } as never,
},
{ isHosted: true, fallbackName: 'brandfetch_search' }
)
).toBe('brandfetch_search <note>API key is hosted by Sim.</note>')
})
})
+25
View File
@@ -0,0 +1,25 @@
import type { ToolConfig } from '@/tools/types'
const HOSTED_API_KEY_NOTE = '<note>API key is hosted by Sim.</note>'
export function getCopilotToolDescription(
tool: Pick<ToolConfig, 'description' | 'hosting' | 'name'>,
options?: {
isHosted?: boolean
fallbackName?: string
}
): string {
const baseDescription = tool.description || tool.name || options?.fallbackName || ''
if (!options?.isHosted || !tool.hosting) {
return baseDescription
}
if (baseDescription.includes(HOSTED_API_KEY_NOTE)) {
return baseDescription
}
return baseDescription
? `${baseDescription} ${HOSTED_API_KEY_NOTE}`
: HOSTED_API_KEY_NOTE
}
@@ -1,9 +1,10 @@
import { existsSync, readFileSync } from 'fs'
import { join } from 'path'
import { createLogger } from '@sim/logger'
import { getCopilotToolDescription } from '@/lib/copilot/tool-descriptions'
import type { BaseServerTool } from '@/lib/copilot/tools/server/base-tool'
import { GetBlocksMetadataInput, GetBlocksMetadataResult } from '@/lib/copilot/tools/shared/schemas'
import { getAllowedIntegrationsFromEnv } from '@/lib/core/config/feature-flags'
import { getAllowedIntegrationsFromEnv, isHosted } from '@/lib/core/config/feature-flags'
import { registry as blockRegistry } from '@/blocks/registry'
import { AuthMode, type BlockConfig, isHiddenFromDisplay } from '@/blocks/types'
import { getUserPermissionConfig } from '@/ee/access-control/utils/permission-check'
@@ -161,7 +162,10 @@ export const getBlocksMetadataServerTool: BaseServerTool<
return {
id: toolId,
name: tool.name || toolId,
description: tool.description || '',
description: getCopilotToolDescription(tool, {
isHosted,
fallbackName: toolId,
}),
inputs: tool.params || {},
outputs: tool.outputs || {},
}
@@ -246,7 +250,12 @@ export const getBlocksMetadataServerTool: BaseServerTool<
operations[opId] = {
toolId: resolvedToolId,
toolName: toolCfg?.name || resolvedToolId,
description: toolCfg?.description || undefined,
description: toolCfg
? getCopilotToolDescription(toolCfg, {
isHosted,
fallbackName: resolvedToolId,
})
: undefined,
inputs: { ...filteredToolParams, ...(operationInputs[opId] || {}) },
outputs: toolOutputs,
inputSchema: operationParameters[opId] || [],
+2 -1
View File
@@ -1,4 +1,5 @@
import { isHosted } from '@/lib/core/config/feature-flags'
import { getCopilotToolDescription } from '@/lib/copilot/tool-descriptions'
import { isSubBlockHiddenByHostedKey } from '@/lib/workflows/subblocks/visibility'
import type { BlockConfig, SubBlockConfig } from '@/blocks/types'
import { PROVIDER_DEFINITIONS } from '@/providers/models'
@@ -721,7 +722,7 @@ export function serializeIntegrationSchema(tool: ToolConfig): string {
{
id: tool.id,
name: tool.name,
description: tool.description,
description: getCopilotToolDescription(tool, { isHosted }),
version: tool.version,
oauth: tool.oauth
? { required: tool.oauth.required, provider: tool.oauth.provider }