(
tool: Tool,
options: {
getToolPermissionContext: () => Promise<ToolPermissionContext>
tools: Tools
agents: AgentDefinition[]
allowedAgentTypes?: string[]
model?: string
/** When true, mark this tool with defer_loading for tool search */
deferLoading?: boolean
cacheControl?: {
type: 'ephemeral'
scope?: 'global' | 'org'
ttl?: '5m' | '1h'
}
},
)
| 117 | } |
| 118 | |
| 119 | export async function toolToAPISchema( |
| 120 | tool: Tool, |
| 121 | options: { |
| 122 | getToolPermissionContext: () => Promise<ToolPermissionContext> |
| 123 | tools: Tools |
| 124 | agents: AgentDefinition[] |
| 125 | allowedAgentTypes?: string[] |
| 126 | model?: string |
| 127 | /** When true, mark this tool with defer_loading for tool search */ |
| 128 | deferLoading?: boolean |
| 129 | cacheControl?: { |
| 130 | type: 'ephemeral' |
| 131 | scope?: 'global' | 'org' |
| 132 | ttl?: '5m' | '1h' |
| 133 | } |
| 134 | }, |
| 135 | ): Promise<BetaToolUnion> { |
| 136 | // Session-stable base schema: name, description, input_schema, strict, |
| 137 | // eager_input_streaming. These are computed once per session and cached to |
| 138 | // prevent mid-session GrowthBook flips (tengu_tool_pear, tengu_fgts) or |
| 139 | // tool.prompt() drift from churning the serialized tool array bytes. |
| 140 | // See toolSchemaCache.ts for rationale. |
| 141 | // |
| 142 | // Cache key includes inputJSONSchema when present. StructuredOutput instances |
| 143 | // share the name 'StructuredOutput' but carry different schemas per workflow |
| 144 | // call — name-only keying returned a stale schema (5.4% → 51% err rate, see |
| 145 | // PR#25424). MCP tools also set inputJSONSchema but each has a stable schema, |
| 146 | // so including it preserves their GB-flip cache stability. |
| 147 | const cacheKey = |
| 148 | 'inputJSONSchema' in tool && tool.inputJSONSchema |
| 149 | ? `${tool.name}:${jsonStringify(tool.inputJSONSchema)}` |
| 150 | : tool.name |
| 151 | const cache = getToolSchemaCache() |
| 152 | let base = cache.get(cacheKey) |
| 153 | if (!base) { |
| 154 | const strictToolsEnabled = |
| 155 | checkStatsigFeatureGate_CACHED_MAY_BE_STALE('tengu_tool_pear') |
| 156 | // Use tool's JSON schema directly if provided, otherwise convert Zod schema |
| 157 | let input_schema = ( |
| 158 | 'inputJSONSchema' in tool && tool.inputJSONSchema |
| 159 | ? tool.inputJSONSchema |
| 160 | : zodToJsonSchema(tool.inputSchema) |
| 161 | ) as Anthropic.Tool.InputSchema |
| 162 | |
| 163 | // Filter out swarm-related fields when swarms are not enabled |
| 164 | // This ensures external non-EAP users don't see swarm features in the schema |
| 165 | if (!isAgentSwarmsEnabled()) { |
| 166 | input_schema = filterSwarmFieldsFromSchema(tool.name, input_schema) |
| 167 | } |
| 168 | |
| 169 | base = { |
| 170 | name: tool.name, |
| 171 | description: await tool.prompt({ |
| 172 | getToolPermissionContext: options.getToolPermissionContext, |
| 173 | tools: options.tools, |
| 174 | agents: options.agents, |
| 175 | allowedAgentTypes: options.allowedAgentTypes, |
| 176 | }), |
no test coverage detected