Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions src/__tests__/casing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown>;

// 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<string, unknown>;

// 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 = {
Expand Down
31 changes: 19 additions & 12 deletions src/__tests__/workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"],
Expand Down Expand Up @@ -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"],
Expand Down Expand Up @@ -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
});
});
});
9 changes: 7 additions & 2 deletions src/shared/elevenlabs-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
});

Expand Down Expand Up @@ -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
});

Expand Down
Loading