diff --git a/src/__tests__/casing.test.ts b/src/__tests__/casing.test.ts index 5e3a9f8..6f3b473 100644 --- a/src/__tests__/casing.test.ts +++ b/src/__tests__/casing.test.ts @@ -204,6 +204,101 @@ describe("Key casing normalization", () => { expect(payload.conversationConfig.agent.prompt.toolIds).toEqual(["tool_789"]); }); + it("createAgentApi camelizes workflow edge conditions (forward_condition, backward_condition)", async () => { + const client = makeMockClient(); + const conversation_config = { + agent: { prompt: { prompt: "hi", temperature: 0 } }, + } as unknown as Record; + + // This is what a workflow looks like after being pulled from the API (snake_case) + const workflow = { + nodes: { + start_node: { type: "start" }, + agent_node: { type: "agent", agent_id: "abc123" } + }, + edges: { + edge_start_to_agent: { + source: "start_node", + target: "agent_node", + forward_condition: { type: "unconditional" } + }, + edge_agent_to_end: { + source: "agent_node", + target: "end_node", + backward_condition: { type: "result", result_key: "success" } + } + } + }; + + await createAgentApi( + client, + "Workflow Agent", + conversation_config, + undefined, + workflow, + [] + ); + + expect(client.conversationalAi.agents.create).toHaveBeenCalledTimes(1); + const payload = (client.conversationalAi.agents.create as jest.Mock).mock.calls[0][0]; + + // Verify workflow edge conditions are converted to camelCase + expect(payload.workflow).toBeDefined(); + expect(payload.workflow.edges.edgeStartToAgent).toEqual({ + source: "start_node", + target: "agent_node", + forwardCondition: { type: "unconditional" } + }); + expect(payload.workflow.edges.edgeAgentToEnd).toEqual({ + source: "agent_node", + target: "end_node", + backwardCondition: { type: "result", resultKey: "success" } + }); + }); + + it("updateAgentApi camelizes workflow edge conditions (forward_condition, backward_condition)", async () => { + const client = makeMockClient(); + const conversation_config = { + agent: { prompt: { prompt: "hi", temperature: 0 } }, + } as unknown as Record; + + // This is what a workflow looks like after being pulled from the API (snake_case) + const workflow = { + nodes: { + start_node: { type: "start" }, + agent_node: { type: "agent", agent_id: "abc123" } + }, + edges: { + edge_start_to_agent: { + source: "start_node", + target: "agent_node", + forward_condition: { type: "llm", description: "When user asks for help" } + } + } + }; + + await updateAgentApi( + client, + "agent_123", + "Workflow Agent", + conversation_config, + undefined, + workflow, + [] + ); + + expect(client.conversationalAi.agents.update).toHaveBeenCalledTimes(1); + const [, payload] = (client.conversationalAi.agents.update as jest.Mock).mock.calls[0]; + + // Verify workflow edge conditions are converted to camelCase + expect(payload.workflow).toBeDefined(); + expect(payload.workflow.edges.edgeStartToAgent).toEqual({ + source: "start_node", + target: "agent_node", + forwardCondition: { type: "llm", description: "When user asks for help" } + }); + }); + it("createAgentApi preserves 'tools' field when 'tool_ids' is not present", async () => { const client = makeMockClient(); const conversation_config = { diff --git a/src/__tests__/workflow.test.ts b/src/__tests__/workflow.test.ts index ac3b59e..0c2c9ef 100644 --- a/src/__tests__/workflow.test.ts +++ b/src/__tests__/workflow.test.ts @@ -92,6 +92,7 @@ describe("Workflow support in agents", () => { expect(client.conversationalAi.agents.create).toHaveBeenCalledTimes(1); const payload = (client.conversationalAi.agents.create as jest.Mock).mock.calls[0][0]; + // Workflow should be converted to camelCase for the API expect(payload).toEqual( expect.objectContaining({ name: "Agent with Workflow", @@ -101,7 +102,7 @@ describe("Workflow support in agents", () => { end: expect.any(Object), }), edges: expect.objectContaining({ - edge_1: expect.any(Object), + edge1: expect.any(Object), // edge_1 becomes edge1 in camelCase }), }), tags: ["workflow"], @@ -174,13 +175,14 @@ describe("Workflow support in agents", () => { ).mock.calls[0]; expect(agentId).toBe("agent_workflow_123"); + // Workflow should be converted to camelCase for the API expect(payload).toEqual( expect.objectContaining({ name: "Updated Agent", workflow: expect.objectContaining({ nodes: expect.objectContaining({ - updated_start: expect.any(Object), - updated_end: expect.any(Object), + updatedStart: expect.any(Object), // updated_start becomes updatedStart + updatedEnd: expect.any(Object), // updated_end becomes updatedEnd }), }), tags: ["updated"], @@ -321,15 +323,20 @@ describe("Workflow support in agents", () => { const payload = (client.conversationalAi.agents.create as jest.Mock).mock.calls[0][0]; - // Verify complex workflow is preserved - expect(payload.workflow).toEqual(complexWorkflow); - expect(payload.workflow.nodes).toHaveProperty("start_1"); - expect(payload.workflow.nodes).toHaveProperty("agent_1"); - expect(payload.workflow.nodes).toHaveProperty("tool_1"); - expect(payload.workflow.nodes).toHaveProperty("end_1"); - expect(payload.workflow.edges).toHaveProperty("edge_start_to_agent"); - expect(payload.workflow.edges).toHaveProperty("edge_agent_to_tool"); - expect(payload.workflow.edges).toHaveProperty("edge_tool_to_end"); + // Workflow should be converted to camelCase for the API + // All snake_case keys become camelCase + expect(payload.workflow.nodes).toHaveProperty("start1"); // start_1 → start1 + expect(payload.workflow.nodes).toHaveProperty("agent1"); // agent_1 → agent1 + expect(payload.workflow.nodes).toHaveProperty("tool1"); // tool_1 → tool1 + expect(payload.workflow.nodes).toHaveProperty("end1"); // end_1 → end1 + expect(payload.workflow.edges).toHaveProperty("edgeStartToAgent"); // edge_start_to_agent → edgeStartToAgent + expect(payload.workflow.edges).toHaveProperty("edgeAgentToTool"); // edge_agent_to_tool → edgeAgentToTool + expect(payload.workflow.edges).toHaveProperty("edgeToolToEnd"); // edge_tool_to_end → edgeToolToEnd + + // Verify nested properties are also converted + expect(payload.workflow.nodes.start1.config).toHaveProperty("initialMessage"); // initial_message → initialMessage + expect(payload.workflow.nodes.agent1).toHaveProperty("agentId"); // agent_id → agentId + expect(payload.workflow.nodes.tool1).toHaveProperty("toolId"); // tool_id → toolId }); }); }); diff --git a/src/shared/elevenlabs-api.ts b/src/shared/elevenlabs-api.ts index 91171ad..1ace94a 100644 --- a/src/shared/elevenlabs-api.ts +++ b/src/shared/elevenlabs-api.ts @@ -117,11 +117,14 @@ export async function createAgentApi( const convConfig = toCamelCaseKeys(cleanedConfig) as ConversationalConfig; const platformSettings = platformSettingsDict && isPlatformSettings(platformSettingsDict) ? toCamelCaseKeys(platformSettingsDict) as AgentPlatformSettingsRequestModel : undefined; + // Normalize workflow to camelCase for API (same as conversationConfig and platformSettings) + const workflowConfig = workflow ? toCamelCaseKeys(workflow) as AgentWorkflowRequestModel : undefined; + const response = await client.conversationalAi.agents.create({ name, conversationConfig: convConfig, platformSettings, - workflow: workflow as AgentWorkflowRequestModel | undefined, + workflow: workflowConfig, tags }); @@ -154,12 +157,14 @@ export async function updateAgentApi( const convConfig = cleanedConfig && isConversationalConfig(cleanedConfig) ? toCamelCaseKeys(cleanedConfig) as ConversationalConfig : undefined; const platformSettings = platformSettingsDict && isPlatformSettings(platformSettingsDict) ? toCamelCaseKeys(platformSettingsDict) as AgentPlatformSettingsRequestModel : undefined; + // Normalize workflow to camelCase for API (same as conversationConfig and platformSettings) + const workflowConfig = workflow ? toCamelCaseKeys(workflow) as AgentWorkflowRequestModel : undefined; const response = await client.conversationalAi.agents.update(agentId, { name, conversationConfig: convConfig, platformSettings, - workflow: workflow as AgentWorkflowRequestModel | undefined, + workflow: workflowConfig, tags });