From ea7d52211da8f03aafffce26e1365057abd61e61 Mon Sep 17 00:00:00 2001 From: Boris Starkov Date: Mon, 10 Nov 2025 17:55:25 +0000 Subject: [PATCH] git tools / tool ids disrepancy --- src/__tests__/casing.test.ts | 98 ++++++++++++++++++++++++++++++++++++ src/agents/templates.ts | 1 - src/shared/elevenlabs-api.ts | 39 +++++++++++++- 3 files changed, 135 insertions(+), 3 deletions(-) diff --git a/src/__tests__/casing.test.ts b/src/__tests__/casing.test.ts index d6dcfcd..5e3a9f8 100644 --- a/src/__tests__/casing.test.ts +++ b/src/__tests__/casing.test.ts @@ -134,4 +134,102 @@ describe("Key casing normalization", () => { }) ); }); + + it("createAgentApi removes deprecated 'tools' field when 'tool_ids' is present", async () => { + const client = makeMockClient(); + const conversation_config = { + conversation: { + client_events: ["audio"], + }, + agent: { + prompt: { + prompt: "hi", + temperature: 0, + tools: [ + { type: "webhook", name: "test_tool", config: {} } + ], + tool_ids: ["tool_123", "tool_456"] + } + }, + } as unknown as Record; + + await createAgentApi( + client, + "Agent with Tools", + conversation_config, + undefined, + undefined, + [] + ); + + expect(client.conversationalAi.agents.create).toHaveBeenCalledTimes(1); + const payload = (client.conversationalAi.agents.create as jest.Mock).mock.calls[0][0]; + + // Verify that 'tools' field is removed but 'toolIds' is present + expect(payload.conversationConfig.agent.prompt).not.toHaveProperty("tools"); + expect(payload.conversationConfig.agent.prompt).toHaveProperty("toolIds"); + expect(payload.conversationConfig.agent.prompt.toolIds).toEqual(["tool_123", "tool_456"]); + }); + + it("updateAgentApi removes deprecated 'tools' field when 'tool_ids' is present", async () => { + const client = makeMockClient(); + const conversation_config = { + agent: { + prompt: { + prompt: "updated", + tools: [ + { type: "system", name: "calendar" } + ], + tool_ids: ["tool_789"] + } + }, + } as unknown as Record; + + await updateAgentApi( + client, + "agent_123", + "Updated Agent", + conversation_config, + undefined, + undefined, + [] + ); + + expect(client.conversationalAi.agents.update).toHaveBeenCalledTimes(1); + const [, payload] = (client.conversationalAi.agents.update as jest.Mock).mock.calls[0]; + + // Verify that 'tools' field is removed but 'toolIds' is present + expect(payload.conversationConfig.agent.prompt).not.toHaveProperty("tools"); + expect(payload.conversationConfig.agent.prompt).toHaveProperty("toolIds"); + expect(payload.conversationConfig.agent.prompt.toolIds).toEqual(["tool_789"]); + }); + + it("createAgentApi preserves 'tools' field when 'tool_ids' is not present", async () => { + const client = makeMockClient(); + const conversation_config = { + agent: { + prompt: { + prompt: "hi", + tools: [ + { type: "webhook", name: "legacy_tool" } + ] + } + }, + } as unknown as Record; + + await createAgentApi( + client, + "Agent with Legacy Tools", + conversation_config, + undefined, + undefined, + [] + ); + + const payload = (client.conversationalAi.agents.create as jest.Mock).mock.calls[0][0]; + + // When tool_ids is not present, tools should be preserved + expect(payload.conversationConfig.agent.prompt).toHaveProperty("tools"); + expect(payload.conversationConfig.agent.prompt.tools).toHaveLength(1); + }); }); diff --git a/src/agents/templates.ts b/src/agents/templates.ts index dcb3a56..16d3997 100644 --- a/src/agents/templates.ts +++ b/src/agents/templates.ts @@ -110,7 +110,6 @@ export function getDefaultAgentTemplate(name: string): AgentConfig { llm: "gemini-2.0-flash", temperature: 0.0, max_tokens: -1, - tools: [], tool_ids: [], mcp_server_ids: [], native_mcp_server_ids: [], diff --git a/src/shared/elevenlabs-api.ts b/src/shared/elevenlabs-api.ts index e8f47b6..9270a6c 100644 --- a/src/shared/elevenlabs-api.ts +++ b/src/shared/elevenlabs-api.ts @@ -16,6 +16,35 @@ function isConversationalConfig(config: unknown): config is ConversationalConfig function isPlatformSettings(settings: unknown): settings is AgentPlatformSettingsRequestModel { return typeof settings === 'object' && settings !== null; } + +/** + * Cleans conversation config before sending to API. + * Removes the deprecated 'tools' field if 'tool_ids' is present to avoid API conflicts. + * The API returns both fields, but only accepts one when creating/updating. + */ +function cleanConversationConfigForApi(config: Record): Record { + const cleaned = { ...config }; + + // Handle nested agent.prompt structure + if (cleaned.agent && typeof cleaned.agent === 'object') { + const agent = { ...(cleaned.agent as Record) }; + + if (agent.prompt && typeof agent.prompt === 'object') { + const prompt = { ...(agent.prompt as Record) }; + + // If tool_ids exists, remove tools (deprecated field) to avoid API error + if (prompt.tool_ids !== undefined || prompt.toolIds !== undefined) { + delete prompt.tools; + } + + agent.prompt = prompt; + } + + cleaned.agent = agent; + } + + return cleaned; +} /** * Gets the API base URL based on residency configuration */ @@ -80,8 +109,11 @@ export async function createAgentApi( throw new Error('Invalid conversation config provided'); } + // Clean config to remove deprecated 'tools' if 'tool_ids' exists + const cleanedConfig = cleanConversationConfigForApi(conversationConfigDict); + // Normalize to camelCase for API - const convConfig = toCamelCaseKeys(conversationConfigDict) as ConversationalConfig; + const convConfig = toCamelCaseKeys(cleanedConfig) as ConversationalConfig; const platformSettings = platformSettingsDict && isPlatformSettings(platformSettingsDict) ? toCamelCaseKeys(platformSettingsDict) as AgentPlatformSettingsRequestModel : undefined; const response = await client.conversationalAi.agents.create({ @@ -116,7 +148,10 @@ export async function updateAgentApi( workflow?: unknown, tags?: string[] ): Promise { - const convConfig = conversationConfigDict && isConversationalConfig(conversationConfigDict) ? toCamelCaseKeys(conversationConfigDict) as ConversationalConfig : undefined; + // Clean config to remove deprecated 'tools' if 'tool_ids' exists + const cleanedConfig = conversationConfigDict ? cleanConversationConfigForApi(conversationConfigDict) : undefined; + + const convConfig = cleanedConfig && isConversationalConfig(cleanedConfig) ? toCamelCaseKeys(cleanedConfig) as ConversationalConfig : undefined; const platformSettings = platformSettingsDict && isPlatformSettings(platformSettingsDict) ? toCamelCaseKeys(platformSettingsDict) as AgentPlatformSettingsRequestModel : undefined; const response = await client.conversationalAi.agents.update(agentId, {