retell-cli 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -0
- package/dist/index.js +2325 -135
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -25,7 +25,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
25
|
|
|
26
26
|
// src/index.ts
|
|
27
27
|
var import_commander = require("commander");
|
|
28
|
-
var
|
|
28
|
+
var import_fs15 = require("fs");
|
|
29
29
|
var import_path6 = require("path");
|
|
30
30
|
|
|
31
31
|
// src/commands/login.ts
|
|
@@ -1375,142 +1375,2332 @@ async function publishAgentCommand(agentId) {
|
|
|
1375
1375
|
}
|
|
1376
1376
|
}
|
|
1377
1377
|
|
|
1378
|
-
// src/
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
);
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1378
|
+
// src/services/tool-resolver.ts
|
|
1379
|
+
async function resolveToolsSource(agentId) {
|
|
1380
|
+
const client = getRetellClient();
|
|
1381
|
+
const agent = await client.agent.retrieve(agentId);
|
|
1382
|
+
if (agent.response_engine.type === "retell-llm") {
|
|
1383
|
+
const llm = await client.llm.retrieve(agent.response_engine.llm_id);
|
|
1384
|
+
const generalTools = llm.general_tools ?? [];
|
|
1385
|
+
const stateTools = {};
|
|
1386
|
+
if (llm.states) {
|
|
1387
|
+
for (const state of llm.states) {
|
|
1388
|
+
const tools2 = state.tools ?? [];
|
|
1389
|
+
if (tools2.length > 0) {
|
|
1390
|
+
stateTools[state.name] = tools2;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
const stateToolCount = Object.values(stateTools).reduce((sum, t) => sum + t.length, 0);
|
|
1395
|
+
const totalCount = generalTools.length + stateToolCount;
|
|
1396
|
+
return {
|
|
1397
|
+
type: "retell-llm",
|
|
1398
|
+
agentId,
|
|
1399
|
+
agentName: agent.agent_name ?? "Unknown",
|
|
1400
|
+
llmId: llm.llm_id,
|
|
1401
|
+
generalTools,
|
|
1402
|
+
stateTools,
|
|
1403
|
+
totalCount
|
|
1404
|
+
};
|
|
1404
1405
|
}
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
`).action(async (callId, options) => {
|
|
1431
|
-
await analyzeTranscriptCommand(callId, {
|
|
1432
|
-
fields: options.fields,
|
|
1433
|
-
raw: options.raw,
|
|
1434
|
-
hotspotsOnly: options.hotspotsOnly,
|
|
1435
|
-
latencyThreshold: options.latencyThreshold ? parseInt(options.latencyThreshold) : void 0,
|
|
1436
|
-
silenceThreshold: options.silenceThreshold ? parseInt(options.silenceThreshold) : void 0
|
|
1437
|
-
});
|
|
1438
|
-
});
|
|
1439
|
-
transcripts.command("search").description("Search transcripts with advanced filtering").option("--status <status>", "Filter by call status (error, ended, ongoing)").option("--agent-id <id>", "Filter by agent ID").option("--since <date>", "Filter calls after this date (YYYY-MM-DD or ISO format)").option("--until <date>", "Filter calls before this date (YYYY-MM-DD or ISO format)").option("--limit <number>", "Maximum number of results (default: 50)", "50").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
|
|
1440
|
-
Examples:
|
|
1441
|
-
$ retell transcripts search --status error
|
|
1442
|
-
$ retell transcripts search --agent-id agent_123 --since 2025-11-01
|
|
1443
|
-
$ retell transcripts search --status error --limit 10
|
|
1444
|
-
$ retell transcripts search --status error --fields call_id,agent_id,call_status
|
|
1445
|
-
$ retell transcripts search --since 2025-11-01 --until 2025-11-15
|
|
1446
|
-
`).action(async (options) => {
|
|
1447
|
-
await searchTranscriptsCommand({
|
|
1448
|
-
status: options.status,
|
|
1449
|
-
agentId: options.agentId,
|
|
1450
|
-
since: options.since,
|
|
1451
|
-
until: options.until,
|
|
1452
|
-
limit: options.limit ? Number(options.limit) : void 0,
|
|
1453
|
-
fields: options.fields
|
|
1454
|
-
});
|
|
1455
|
-
});
|
|
1456
|
-
var agents = program.command("agents").description("Manage agents");
|
|
1457
|
-
agents.command("list").description("List all agents").option("-l, --limit <number>", "Maximum number of agents to return (default: 100)", "100").option("--fields <fields>", "Comma-separated list of fields to return (e.g., agent_id,agent_name,response_engine_type)").addHelpText("after", `
|
|
1458
|
-
Examples:
|
|
1459
|
-
$ retell agents list
|
|
1460
|
-
$ retell agents list --limit 10
|
|
1461
|
-
$ retell agents list --fields agent_id,agent_name
|
|
1462
|
-
$ retell agents list | jq '.[] | select(.response_engine.type == "retell-llm")'
|
|
1463
|
-
`).action(async (options) => {
|
|
1464
|
-
const limit = parseInt(options.limit, 10);
|
|
1465
|
-
if (isNaN(limit) || limit < 1) {
|
|
1466
|
-
console.error("Error: limit must be a positive number");
|
|
1467
|
-
process.exit(1);
|
|
1406
|
+
if (agent.response_engine.type === "conversation-flow") {
|
|
1407
|
+
const flow = await client.conversationFlow.retrieve(
|
|
1408
|
+
agent.response_engine.conversation_flow_id
|
|
1409
|
+
);
|
|
1410
|
+
const flowTools = flow.tools ?? [];
|
|
1411
|
+
const componentTools = {};
|
|
1412
|
+
if (flow.components) {
|
|
1413
|
+
for (const component of flow.components) {
|
|
1414
|
+
const tools2 = component.tools ?? [];
|
|
1415
|
+
if (tools2.length > 0) {
|
|
1416
|
+
componentTools[component.name] = tools2;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
const componentToolCount = Object.values(componentTools).reduce((sum, t) => sum + t.length, 0);
|
|
1421
|
+
const totalCount = flowTools.length + componentToolCount;
|
|
1422
|
+
return {
|
|
1423
|
+
type: "conversation-flow",
|
|
1424
|
+
agentId,
|
|
1425
|
+
agentName: agent.agent_name ?? "Unknown",
|
|
1426
|
+
flowId: flow.conversation_flow_id,
|
|
1427
|
+
flowTools,
|
|
1428
|
+
componentTools,
|
|
1429
|
+
totalCount
|
|
1430
|
+
};
|
|
1468
1431
|
}
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1432
|
+
return {
|
|
1433
|
+
type: "custom-llm",
|
|
1434
|
+
error: "Custom LLM agents cannot be managed via API. Use the Retell dashboard to manage tools for custom LLM agents."
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
async function findToolInLlm(agentId, toolName, stateName) {
|
|
1438
|
+
const source = await resolveToolsSource(agentId);
|
|
1439
|
+
if (source.type !== "retell-llm") {
|
|
1440
|
+
return null;
|
|
1441
|
+
}
|
|
1442
|
+
if (stateName) {
|
|
1443
|
+
const stateTools = source.stateTools[stateName];
|
|
1444
|
+
if (stateTools) {
|
|
1445
|
+
const tool = stateTools.find((t) => t.name === toolName);
|
|
1446
|
+
if (tool) {
|
|
1447
|
+
return {
|
|
1448
|
+
tool,
|
|
1449
|
+
location: { location: "state", stateName }
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
return null;
|
|
1454
|
+
}
|
|
1455
|
+
const generalTool = source.generalTools.find((t) => t.name === toolName);
|
|
1456
|
+
if (generalTool) {
|
|
1457
|
+
return {
|
|
1458
|
+
tool: generalTool,
|
|
1459
|
+
location: { location: "general" }
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
for (const [state, tools2] of Object.entries(source.stateTools)) {
|
|
1463
|
+
const tool = tools2.find((t) => t.name === toolName);
|
|
1464
|
+
if (tool) {
|
|
1465
|
+
return {
|
|
1466
|
+
tool,
|
|
1467
|
+
location: { location: "state", stateName: state }
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
return null;
|
|
1472
|
+
}
|
|
1473
|
+
async function findToolInFlow(agentId, toolName, componentId) {
|
|
1474
|
+
const source = await resolveToolsSource(agentId);
|
|
1475
|
+
if (source.type !== "conversation-flow") {
|
|
1476
|
+
return null;
|
|
1477
|
+
}
|
|
1478
|
+
if (componentId) {
|
|
1479
|
+
const componentTools = source.componentTools[componentId];
|
|
1480
|
+
if (componentTools) {
|
|
1481
|
+
const tool = componentTools.find((t) => t.name === toolName);
|
|
1482
|
+
if (tool) {
|
|
1483
|
+
return {
|
|
1484
|
+
tool,
|
|
1485
|
+
location: { location: "component", componentId }
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
return null;
|
|
1490
|
+
}
|
|
1491
|
+
const flowTool = source.flowTools.find((t) => t.name === toolName);
|
|
1492
|
+
if (flowTool) {
|
|
1493
|
+
return {
|
|
1494
|
+
tool: flowTool,
|
|
1495
|
+
location: { location: "flow" }
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
for (const [compId, tools2] of Object.entries(source.componentTools)) {
|
|
1499
|
+
const tool = tools2.find((t) => t.name === toolName);
|
|
1500
|
+
if (tool) {
|
|
1501
|
+
return {
|
|
1502
|
+
tool,
|
|
1503
|
+
location: { location: "component", componentId: compId }
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
return null;
|
|
1508
|
+
}
|
|
1509
|
+
async function findTool(agentId, toolName, locationHint) {
|
|
1510
|
+
const source = await resolveToolsSource(agentId);
|
|
1511
|
+
if (source.type === "custom-llm") {
|
|
1512
|
+
return null;
|
|
1513
|
+
}
|
|
1514
|
+
if (source.type === "retell-llm") {
|
|
1515
|
+
const result = await findToolInLlm(agentId, toolName, locationHint);
|
|
1516
|
+
if (result) {
|
|
1517
|
+
return {
|
|
1518
|
+
tool: result,
|
|
1519
|
+
agentName: source.agentName,
|
|
1520
|
+
engineType: "retell-llm"
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
if (source.type === "conversation-flow") {
|
|
1525
|
+
const result = await findToolInFlow(agentId, toolName, locationHint);
|
|
1526
|
+
if (result) {
|
|
1527
|
+
return {
|
|
1528
|
+
tool: result,
|
|
1529
|
+
agentName: source.agentName,
|
|
1530
|
+
engineType: "conversation-flow"
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
return null;
|
|
1535
|
+
}
|
|
1536
|
+
function summarizeTool(tool) {
|
|
1537
|
+
return {
|
|
1538
|
+
name: tool.name,
|
|
1539
|
+
type: tool.type,
|
|
1540
|
+
description: tool.description
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
function getAllToolNames(source) {
|
|
1544
|
+
if (source.type === "custom-llm") {
|
|
1545
|
+
return [];
|
|
1546
|
+
}
|
|
1547
|
+
if (source.type === "retell-llm") {
|
|
1548
|
+
const names = source.generalTools.map((t) => t.name);
|
|
1549
|
+
for (const tools2 of Object.values(source.stateTools)) {
|
|
1550
|
+
names.push(...tools2.map((t) => t.name));
|
|
1551
|
+
}
|
|
1552
|
+
return names;
|
|
1553
|
+
}
|
|
1554
|
+
if (source.type === "conversation-flow") {
|
|
1555
|
+
const names = source.flowTools.map((t) => t.name);
|
|
1556
|
+
for (const tools2 of Object.values(source.componentTools)) {
|
|
1557
|
+
names.push(...tools2.map((t) => t.name));
|
|
1558
|
+
}
|
|
1559
|
+
return names;
|
|
1560
|
+
}
|
|
1561
|
+
return [];
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
// src/commands/tools/list.ts
|
|
1565
|
+
async function listToolsCommand(agentId, options) {
|
|
1566
|
+
try {
|
|
1567
|
+
const source = await resolveToolsSource(agentId);
|
|
1568
|
+
if (source.type === "custom-llm") {
|
|
1569
|
+
outputError(source.error, "CUSTOM_LLM_NOT_SUPPORTED");
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
let output;
|
|
1573
|
+
if (source.type === "retell-llm") {
|
|
1574
|
+
if (options.state) {
|
|
1575
|
+
const stateTools = source.stateTools[options.state];
|
|
1576
|
+
if (!stateTools) {
|
|
1577
|
+
outputError(
|
|
1578
|
+
`State '${options.state}' not found. Available states: ${Object.keys(source.stateTools).join(", ") || "none"}`,
|
|
1579
|
+
"STATE_NOT_FOUND"
|
|
1580
|
+
);
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
output = {
|
|
1584
|
+
agent_id: source.agentId,
|
|
1585
|
+
agent_name: source.agentName,
|
|
1586
|
+
engine_type: "retell-llm",
|
|
1587
|
+
llm_id: source.llmId,
|
|
1588
|
+
general_tools: [],
|
|
1589
|
+
state_tools: {
|
|
1590
|
+
[options.state]: stateTools.map(summarizeTool)
|
|
1591
|
+
},
|
|
1592
|
+
total_count: stateTools.length
|
|
1593
|
+
};
|
|
1594
|
+
} else {
|
|
1595
|
+
const stateToolsSummary = {};
|
|
1596
|
+
for (const [stateName, tools2] of Object.entries(source.stateTools)) {
|
|
1597
|
+
stateToolsSummary[stateName] = tools2.map(summarizeTool);
|
|
1598
|
+
}
|
|
1599
|
+
output = {
|
|
1600
|
+
agent_id: source.agentId,
|
|
1601
|
+
agent_name: source.agentName,
|
|
1602
|
+
engine_type: "retell-llm",
|
|
1603
|
+
llm_id: source.llmId,
|
|
1604
|
+
general_tools: source.generalTools.map(summarizeTool),
|
|
1605
|
+
state_tools: stateToolsSummary,
|
|
1606
|
+
total_count: source.totalCount
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
} else {
|
|
1610
|
+
if (options.component) {
|
|
1611
|
+
const componentTools = source.componentTools[options.component];
|
|
1612
|
+
if (!componentTools) {
|
|
1613
|
+
outputError(
|
|
1614
|
+
`Component '${options.component}' not found. Available components: ${Object.keys(source.componentTools).join(", ") || "none"}`,
|
|
1615
|
+
"COMPONENT_NOT_FOUND"
|
|
1616
|
+
);
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
output = {
|
|
1620
|
+
agent_id: source.agentId,
|
|
1621
|
+
agent_name: source.agentName,
|
|
1622
|
+
engine_type: "conversation-flow",
|
|
1623
|
+
flow_id: source.flowId,
|
|
1624
|
+
flow_tools: [],
|
|
1625
|
+
component_tools: {
|
|
1626
|
+
[options.component]: componentTools.map(summarizeTool)
|
|
1627
|
+
},
|
|
1628
|
+
total_count: componentTools.length
|
|
1629
|
+
};
|
|
1630
|
+
} else {
|
|
1631
|
+
const componentToolsSummary = {};
|
|
1632
|
+
for (const [compId, tools2] of Object.entries(source.componentTools)) {
|
|
1633
|
+
componentToolsSummary[compId] = tools2.map(summarizeTool);
|
|
1634
|
+
}
|
|
1635
|
+
output = {
|
|
1636
|
+
agent_id: source.agentId,
|
|
1637
|
+
agent_name: source.agentName,
|
|
1638
|
+
engine_type: "conversation-flow",
|
|
1639
|
+
flow_id: source.flowId,
|
|
1640
|
+
flow_tools: source.flowTools.map(summarizeTool),
|
|
1641
|
+
component_tools: componentToolsSummary,
|
|
1642
|
+
total_count: source.totalCount
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
if (options.fields) {
|
|
1647
|
+
const filtered = filterFields(output, options.fields.split(",").map((f) => f.trim()));
|
|
1648
|
+
outputJson(filtered);
|
|
1649
|
+
} else {
|
|
1650
|
+
outputJson(output);
|
|
1651
|
+
}
|
|
1652
|
+
} catch (error) {
|
|
1653
|
+
handleSdkError(error);
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
// src/commands/tools/get.ts
|
|
1658
|
+
async function getToolCommand(agentId, toolName, options) {
|
|
1659
|
+
try {
|
|
1660
|
+
const source = await resolveToolsSource(agentId);
|
|
1661
|
+
if (source.type === "custom-llm") {
|
|
1662
|
+
outputError(source.error, "CUSTOM_LLM_NOT_SUPPORTED");
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
const locationHint = source.type === "retell-llm" ? options.state : options.component;
|
|
1666
|
+
const result = await findTool(agentId, toolName, locationHint);
|
|
1667
|
+
if (!result) {
|
|
1668
|
+
let errorMsg = `Tool '${toolName}' not found`;
|
|
1669
|
+
if (locationHint) {
|
|
1670
|
+
errorMsg += ` in ${source.type === "retell-llm" ? "state" : "component"} '${locationHint}'`;
|
|
1671
|
+
}
|
|
1672
|
+
outputError(errorMsg, "TOOL_NOT_FOUND");
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1675
|
+
const output = {
|
|
1676
|
+
agent_id: agentId,
|
|
1677
|
+
agent_name: result.agentName,
|
|
1678
|
+
tool_name: toolName,
|
|
1679
|
+
location: result.tool.location,
|
|
1680
|
+
tool: result.tool.tool
|
|
1681
|
+
};
|
|
1682
|
+
if (options.fields) {
|
|
1683
|
+
const filtered = filterFields(output, options.fields.split(",").map((f) => f.trim()));
|
|
1684
|
+
outputJson(filtered);
|
|
1685
|
+
} else {
|
|
1686
|
+
outputJson(output);
|
|
1687
|
+
}
|
|
1688
|
+
} catch (error) {
|
|
1689
|
+
handleSdkError(error);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
// src/commands/tools/add.ts
|
|
1694
|
+
var import_fs5 = require("fs");
|
|
1695
|
+
async function addToolCommand(agentId, options) {
|
|
1696
|
+
try {
|
|
1697
|
+
if (!(0, import_fs5.existsSync)(options.file)) {
|
|
1698
|
+
outputError(`Tool file not found: ${options.file}`, "FILE_NOT_FOUND");
|
|
1699
|
+
return;
|
|
1700
|
+
}
|
|
1701
|
+
let tool;
|
|
1702
|
+
try {
|
|
1703
|
+
const content = (0, import_fs5.readFileSync)(options.file, "utf-8");
|
|
1704
|
+
tool = JSON.parse(content);
|
|
1705
|
+
} catch (error) {
|
|
1706
|
+
if (error instanceof SyntaxError) {
|
|
1707
|
+
outputError(`Invalid JSON in tool file: ${error.message}`, "INVALID_JSON");
|
|
1708
|
+
} else {
|
|
1709
|
+
outputError(`Error reading tool file: ${error.message}`, "FILE_READ_ERROR");
|
|
1710
|
+
}
|
|
1711
|
+
return;
|
|
1712
|
+
}
|
|
1713
|
+
if (!tool.name) {
|
|
1714
|
+
outputError('Tool must have a "name" field', "INVALID_TOOL");
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
if (!tool.type) {
|
|
1718
|
+
outputError('Tool must have a "type" field', "INVALID_TOOL");
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
const source = await resolveToolsSource(agentId);
|
|
1722
|
+
if (source.type === "custom-llm") {
|
|
1723
|
+
outputError(source.error, "CUSTOM_LLM_NOT_SUPPORTED");
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1726
|
+
const existingNames = getAllToolNames(source);
|
|
1727
|
+
if (existingNames.includes(tool.name)) {
|
|
1728
|
+
outputError(
|
|
1729
|
+
`Tool with name '${tool.name}' already exists. Use 'retell tools update' to modify it.`,
|
|
1730
|
+
"DUPLICATE_TOOL"
|
|
1731
|
+
);
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
const client = getRetellClient();
|
|
1735
|
+
if (source.type === "retell-llm") {
|
|
1736
|
+
if (options.dryRun) {
|
|
1737
|
+
const location = options.state ? `state '${options.state}'` : "general tools";
|
|
1738
|
+
outputJson({
|
|
1739
|
+
message: "Dry run - no changes applied",
|
|
1740
|
+
agent_id: agentId,
|
|
1741
|
+
agent_name: source.agentName,
|
|
1742
|
+
tool_name: tool.name,
|
|
1743
|
+
operation: "add",
|
|
1744
|
+
location: options.state ? { location: "state", stateName: options.state } : { location: "general" },
|
|
1745
|
+
tool_preview: tool,
|
|
1746
|
+
would_add_to: location
|
|
1747
|
+
});
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1750
|
+
const llm = await client.llm.retrieve(source.llmId);
|
|
1751
|
+
if (options.state) {
|
|
1752
|
+
const states = llm.states ?? [];
|
|
1753
|
+
const stateIndex = states.findIndex((s) => s.name === options.state);
|
|
1754
|
+
if (stateIndex === -1) {
|
|
1755
|
+
outputError(
|
|
1756
|
+
`State '${options.state}' not found. Available states: ${states.map((s) => s.name).join(", ") || "none"}`,
|
|
1757
|
+
"STATE_NOT_FOUND"
|
|
1758
|
+
);
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
const updatedStates = [...states];
|
|
1762
|
+
const stateTools = [...updatedStates[stateIndex].tools ?? []];
|
|
1763
|
+
stateTools.push(tool);
|
|
1764
|
+
updatedStates[stateIndex] = { ...updatedStates[stateIndex], tools: stateTools };
|
|
1765
|
+
await client.llm.update(source.llmId, { states: updatedStates });
|
|
1766
|
+
const output = {
|
|
1767
|
+
message: "Tool added successfully (draft version)",
|
|
1768
|
+
agent_id: agentId,
|
|
1769
|
+
agent_name: source.agentName,
|
|
1770
|
+
tool_name: tool.name,
|
|
1771
|
+
operation: "add",
|
|
1772
|
+
location: { location: "state", stateName: options.state },
|
|
1773
|
+
note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
|
|
1774
|
+
};
|
|
1775
|
+
outputJson(output);
|
|
1776
|
+
} else {
|
|
1777
|
+
const generalTools = [...llm.general_tools ?? []];
|
|
1778
|
+
generalTools.push(tool);
|
|
1779
|
+
await client.llm.update(source.llmId, { general_tools: generalTools });
|
|
1780
|
+
const output = {
|
|
1781
|
+
message: "Tool added successfully (draft version)",
|
|
1782
|
+
agent_id: agentId,
|
|
1783
|
+
agent_name: source.agentName,
|
|
1784
|
+
tool_name: tool.name,
|
|
1785
|
+
operation: "add",
|
|
1786
|
+
location: { location: "general" },
|
|
1787
|
+
note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
|
|
1788
|
+
};
|
|
1789
|
+
outputJson(output);
|
|
1790
|
+
}
|
|
1791
|
+
} else {
|
|
1792
|
+
if (options.dryRun) {
|
|
1793
|
+
const location = options.component ? `component '${options.component}'` : "flow tools";
|
|
1794
|
+
outputJson({
|
|
1795
|
+
message: "Dry run - no changes applied",
|
|
1796
|
+
agent_id: agentId,
|
|
1797
|
+
agent_name: source.agentName,
|
|
1798
|
+
tool_name: tool.name,
|
|
1799
|
+
operation: "add",
|
|
1800
|
+
location: options.component ? { location: "component", componentId: options.component } : { location: "flow" },
|
|
1801
|
+
tool_preview: tool,
|
|
1802
|
+
would_add_to: location
|
|
1803
|
+
});
|
|
1804
|
+
return;
|
|
1805
|
+
}
|
|
1806
|
+
const flow = await client.conversationFlow.retrieve(source.flowId);
|
|
1807
|
+
if (options.component) {
|
|
1808
|
+
const components = flow.components ?? [];
|
|
1809
|
+
const compIndex = components.findIndex((c) => c.name === options.component);
|
|
1810
|
+
if (compIndex === -1) {
|
|
1811
|
+
outputError(
|
|
1812
|
+
`Component '${options.component}' not found. Available components: ${components.map((c) => c.name).join(", ") || "none"}`,
|
|
1813
|
+
"COMPONENT_NOT_FOUND"
|
|
1814
|
+
);
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
const updatedComponents = [...components];
|
|
1818
|
+
const compTools = [...updatedComponents[compIndex].tools ?? []];
|
|
1819
|
+
compTools.push(tool);
|
|
1820
|
+
updatedComponents[compIndex] = { ...updatedComponents[compIndex], tools: compTools };
|
|
1821
|
+
await client.conversationFlow.update(source.flowId, { components: updatedComponents });
|
|
1822
|
+
const output = {
|
|
1823
|
+
message: "Tool added successfully (draft version)",
|
|
1824
|
+
agent_id: agentId,
|
|
1825
|
+
agent_name: source.agentName,
|
|
1826
|
+
tool_name: tool.name,
|
|
1827
|
+
operation: "add",
|
|
1828
|
+
location: { location: "component", componentId: options.component },
|
|
1829
|
+
note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
|
|
1830
|
+
};
|
|
1831
|
+
outputJson(output);
|
|
1832
|
+
} else {
|
|
1833
|
+
const flowTools = [...flow.tools ?? []];
|
|
1834
|
+
flowTools.push(tool);
|
|
1835
|
+
await client.conversationFlow.update(source.flowId, { tools: flowTools });
|
|
1836
|
+
const output = {
|
|
1837
|
+
message: "Tool added successfully (draft version)",
|
|
1838
|
+
agent_id: agentId,
|
|
1839
|
+
agent_name: source.agentName,
|
|
1840
|
+
tool_name: tool.name,
|
|
1841
|
+
operation: "add",
|
|
1842
|
+
location: { location: "flow" },
|
|
1843
|
+
note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
|
|
1844
|
+
};
|
|
1845
|
+
outputJson(output);
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
} catch (error) {
|
|
1849
|
+
if (error instanceof SyntaxError) {
|
|
1850
|
+
outputError(`Invalid JSON: ${error.message}`, "INVALID_JSON");
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
handleSdkError(error);
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
// src/commands/tools/update.ts
|
|
1858
|
+
var import_fs6 = require("fs");
|
|
1859
|
+
async function updateToolCommand(agentId, toolName, options) {
|
|
1860
|
+
try {
|
|
1861
|
+
if (!(0, import_fs6.existsSync)(options.file)) {
|
|
1862
|
+
outputError(`Tool file not found: ${options.file}`, "FILE_NOT_FOUND");
|
|
1863
|
+
return;
|
|
1864
|
+
}
|
|
1865
|
+
let newTool;
|
|
1866
|
+
try {
|
|
1867
|
+
const content = (0, import_fs6.readFileSync)(options.file, "utf-8");
|
|
1868
|
+
newTool = JSON.parse(content);
|
|
1869
|
+
} catch (error) {
|
|
1870
|
+
if (error instanceof SyntaxError) {
|
|
1871
|
+
outputError(`Invalid JSON in tool file: ${error.message}`, "INVALID_JSON");
|
|
1872
|
+
} else {
|
|
1873
|
+
outputError(`Error reading tool file: ${error.message}`, "FILE_READ_ERROR");
|
|
1874
|
+
}
|
|
1875
|
+
return;
|
|
1876
|
+
}
|
|
1877
|
+
if (!newTool.name) {
|
|
1878
|
+
outputError('Tool must have a "name" field', "INVALID_TOOL");
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
if (!newTool.type) {
|
|
1882
|
+
outputError('Tool must have a "type" field', "INVALID_TOOL");
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
const source = await resolveToolsSource(agentId);
|
|
1886
|
+
if (source.type === "custom-llm") {
|
|
1887
|
+
outputError(source.error, "CUSTOM_LLM_NOT_SUPPORTED");
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
const locationHint = source.type === "retell-llm" ? options.state : options.component;
|
|
1891
|
+
const existingTool = await findTool(agentId, toolName, locationHint);
|
|
1892
|
+
if (!existingTool) {
|
|
1893
|
+
let errorMsg = `Tool '${toolName}' not found`;
|
|
1894
|
+
if (locationHint) {
|
|
1895
|
+
errorMsg += ` in ${source.type === "retell-llm" ? "state" : "component"} '${locationHint}'`;
|
|
1896
|
+
}
|
|
1897
|
+
outputError(errorMsg, "TOOL_NOT_FOUND");
|
|
1898
|
+
return;
|
|
1899
|
+
}
|
|
1900
|
+
const client = getRetellClient();
|
|
1901
|
+
if (source.type === "retell-llm") {
|
|
1902
|
+
if (options.dryRun) {
|
|
1903
|
+
outputJson({
|
|
1904
|
+
message: "Dry run - no changes applied",
|
|
1905
|
+
agent_id: agentId,
|
|
1906
|
+
agent_name: source.agentName,
|
|
1907
|
+
tool_name: toolName,
|
|
1908
|
+
operation: "update",
|
|
1909
|
+
location: existingTool.tool.location,
|
|
1910
|
+
old_tool: existingTool.tool.tool,
|
|
1911
|
+
new_tool: newTool
|
|
1912
|
+
});
|
|
1913
|
+
return;
|
|
1914
|
+
}
|
|
1915
|
+
const llm = await client.llm.retrieve(source.llmId);
|
|
1916
|
+
if (existingTool.tool.location.location === "state") {
|
|
1917
|
+
const stateName = existingTool.tool.location.stateName;
|
|
1918
|
+
const states = llm.states ?? [];
|
|
1919
|
+
const stateIndex = states.findIndex((s) => s.name === stateName);
|
|
1920
|
+
if (stateIndex === -1) {
|
|
1921
|
+
outputError(`State '${stateName}' not found`, "STATE_NOT_FOUND");
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
const updatedStates = [...states];
|
|
1925
|
+
const stateTools = [...updatedStates[stateIndex].tools ?? []];
|
|
1926
|
+
const toolIndex = stateTools.findIndex((t) => t.name === toolName);
|
|
1927
|
+
if (toolIndex === -1) {
|
|
1928
|
+
outputError(`Tool '${toolName}' not found in state '${stateName}'`, "TOOL_NOT_FOUND");
|
|
1929
|
+
return;
|
|
1930
|
+
}
|
|
1931
|
+
stateTools[toolIndex] = newTool;
|
|
1932
|
+
updatedStates[stateIndex] = { ...updatedStates[stateIndex], tools: stateTools };
|
|
1933
|
+
await client.llm.update(source.llmId, { states: updatedStates });
|
|
1934
|
+
const output = {
|
|
1935
|
+
message: "Tool updated successfully (draft version)",
|
|
1936
|
+
agent_id: agentId,
|
|
1937
|
+
agent_name: source.agentName,
|
|
1938
|
+
tool_name: newTool.name,
|
|
1939
|
+
operation: "update",
|
|
1940
|
+
location: { location: "state", stateName },
|
|
1941
|
+
note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
|
|
1942
|
+
};
|
|
1943
|
+
outputJson(output);
|
|
1944
|
+
} else {
|
|
1945
|
+
const generalTools = [...llm.general_tools ?? []];
|
|
1946
|
+
const toolIndex = generalTools.findIndex((t) => t.name === toolName);
|
|
1947
|
+
if (toolIndex === -1) {
|
|
1948
|
+
outputError(`Tool '${toolName}' not found in general tools`, "TOOL_NOT_FOUND");
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
generalTools[toolIndex] = newTool;
|
|
1952
|
+
await client.llm.update(source.llmId, { general_tools: generalTools });
|
|
1953
|
+
const output = {
|
|
1954
|
+
message: "Tool updated successfully (draft version)",
|
|
1955
|
+
agent_id: agentId,
|
|
1956
|
+
agent_name: source.agentName,
|
|
1957
|
+
tool_name: newTool.name,
|
|
1958
|
+
operation: "update",
|
|
1959
|
+
location: { location: "general" },
|
|
1960
|
+
note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
|
|
1961
|
+
};
|
|
1962
|
+
outputJson(output);
|
|
1963
|
+
}
|
|
1964
|
+
} else {
|
|
1965
|
+
if (options.dryRun) {
|
|
1966
|
+
outputJson({
|
|
1967
|
+
message: "Dry run - no changes applied",
|
|
1968
|
+
agent_id: agentId,
|
|
1969
|
+
agent_name: source.agentName,
|
|
1970
|
+
tool_name: toolName,
|
|
1971
|
+
operation: "update",
|
|
1972
|
+
location: existingTool.tool.location,
|
|
1973
|
+
old_tool: existingTool.tool.tool,
|
|
1974
|
+
new_tool: newTool
|
|
1975
|
+
});
|
|
1976
|
+
return;
|
|
1977
|
+
}
|
|
1978
|
+
const flow = await client.conversationFlow.retrieve(source.flowId);
|
|
1979
|
+
if (existingTool.tool.location.location === "component") {
|
|
1980
|
+
const componentId = existingTool.tool.location.componentId;
|
|
1981
|
+
const components = flow.components ?? [];
|
|
1982
|
+
const compIndex = components.findIndex((c) => c.name === componentId);
|
|
1983
|
+
if (compIndex === -1) {
|
|
1984
|
+
outputError(`Component '${componentId}' not found`, "COMPONENT_NOT_FOUND");
|
|
1985
|
+
return;
|
|
1986
|
+
}
|
|
1987
|
+
const updatedComponents = [...components];
|
|
1988
|
+
const compTools = [...updatedComponents[compIndex].tools ?? []];
|
|
1989
|
+
const toolIndex = compTools.findIndex((t) => t.name === toolName);
|
|
1990
|
+
if (toolIndex === -1) {
|
|
1991
|
+
outputError(`Tool '${toolName}' not found in component '${componentId}'`, "TOOL_NOT_FOUND");
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
compTools[toolIndex] = newTool;
|
|
1995
|
+
updatedComponents[compIndex] = { ...updatedComponents[compIndex], tools: compTools };
|
|
1996
|
+
await client.conversationFlow.update(source.flowId, { components: updatedComponents });
|
|
1997
|
+
const output = {
|
|
1998
|
+
message: "Tool updated successfully (draft version)",
|
|
1999
|
+
agent_id: agentId,
|
|
2000
|
+
agent_name: source.agentName,
|
|
2001
|
+
tool_name: newTool.name,
|
|
2002
|
+
operation: "update",
|
|
2003
|
+
location: { location: "component", componentId },
|
|
2004
|
+
note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
|
|
2005
|
+
};
|
|
2006
|
+
outputJson(output);
|
|
2007
|
+
} else {
|
|
2008
|
+
const flowTools = [...flow.tools ?? []];
|
|
2009
|
+
const toolIndex = flowTools.findIndex((t) => t.name === toolName);
|
|
2010
|
+
if (toolIndex === -1) {
|
|
2011
|
+
outputError(`Tool '${toolName}' not found in flow tools`, "TOOL_NOT_FOUND");
|
|
2012
|
+
return;
|
|
2013
|
+
}
|
|
2014
|
+
flowTools[toolIndex] = newTool;
|
|
2015
|
+
await client.conversationFlow.update(source.flowId, { tools: flowTools });
|
|
2016
|
+
const output = {
|
|
2017
|
+
message: "Tool updated successfully (draft version)",
|
|
2018
|
+
agent_id: agentId,
|
|
2019
|
+
agent_name: source.agentName,
|
|
2020
|
+
tool_name: newTool.name,
|
|
2021
|
+
operation: "update",
|
|
2022
|
+
location: { location: "flow" },
|
|
2023
|
+
note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
|
|
2024
|
+
};
|
|
2025
|
+
outputJson(output);
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
} catch (error) {
|
|
2029
|
+
if (error instanceof SyntaxError) {
|
|
2030
|
+
outputError(`Invalid JSON: ${error.message}`, "INVALID_JSON");
|
|
2031
|
+
return;
|
|
2032
|
+
}
|
|
2033
|
+
handleSdkError(error);
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
// src/commands/tools/remove.ts
|
|
2038
|
+
async function removeToolCommand(agentId, toolName, options) {
|
|
2039
|
+
try {
|
|
2040
|
+
const source = await resolveToolsSource(agentId);
|
|
2041
|
+
if (source.type === "custom-llm") {
|
|
2042
|
+
outputError(source.error, "CUSTOM_LLM_NOT_SUPPORTED");
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
const locationHint = source.type === "retell-llm" ? options.state : options.component;
|
|
2046
|
+
const existingTool = await findTool(agentId, toolName, locationHint);
|
|
2047
|
+
if (!existingTool) {
|
|
2048
|
+
let errorMsg = `Tool '${toolName}' not found`;
|
|
2049
|
+
if (locationHint) {
|
|
2050
|
+
errorMsg += ` in ${source.type === "retell-llm" ? "state" : "component"} '${locationHint}'`;
|
|
2051
|
+
}
|
|
2052
|
+
outputError(errorMsg, "TOOL_NOT_FOUND");
|
|
2053
|
+
return;
|
|
2054
|
+
}
|
|
2055
|
+
const client = getRetellClient();
|
|
2056
|
+
if (source.type === "retell-llm") {
|
|
2057
|
+
if (options.dryRun) {
|
|
2058
|
+
outputJson({
|
|
2059
|
+
message: "Dry run - no changes applied",
|
|
2060
|
+
agent_id: agentId,
|
|
2061
|
+
agent_name: source.agentName,
|
|
2062
|
+
tool_name: toolName,
|
|
2063
|
+
operation: "remove",
|
|
2064
|
+
location: existingTool.tool.location,
|
|
2065
|
+
tool_to_remove: existingTool.tool.tool
|
|
2066
|
+
});
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
2069
|
+
const llm = await client.llm.retrieve(source.llmId);
|
|
2070
|
+
if (existingTool.tool.location.location === "state") {
|
|
2071
|
+
const stateName = existingTool.tool.location.stateName;
|
|
2072
|
+
const states = llm.states ?? [];
|
|
2073
|
+
const stateIndex = states.findIndex((s) => s.name === stateName);
|
|
2074
|
+
if (stateIndex === -1) {
|
|
2075
|
+
outputError(`State '${stateName}' not found`, "STATE_NOT_FOUND");
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
const updatedStates = [...states];
|
|
2079
|
+
const stateTools = [...updatedStates[stateIndex].tools ?? []];
|
|
2080
|
+
const toolIndex = stateTools.findIndex((t) => t.name === toolName);
|
|
2081
|
+
if (toolIndex === -1) {
|
|
2082
|
+
outputError(`Tool '${toolName}' not found in state '${stateName}'`, "TOOL_NOT_FOUND");
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
stateTools.splice(toolIndex, 1);
|
|
2086
|
+
updatedStates[stateIndex] = { ...updatedStates[stateIndex], tools: stateTools };
|
|
2087
|
+
await client.llm.update(source.llmId, { states: updatedStates });
|
|
2088
|
+
const output = {
|
|
2089
|
+
message: "Tool removed successfully (draft version)",
|
|
2090
|
+
agent_id: agentId,
|
|
2091
|
+
agent_name: source.agentName,
|
|
2092
|
+
tool_name: toolName,
|
|
2093
|
+
operation: "remove",
|
|
2094
|
+
location: { location: "state", stateName },
|
|
2095
|
+
note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
|
|
2096
|
+
};
|
|
2097
|
+
outputJson(output);
|
|
2098
|
+
} else {
|
|
2099
|
+
const generalTools = [...llm.general_tools ?? []];
|
|
2100
|
+
const toolIndex = generalTools.findIndex((t) => t.name === toolName);
|
|
2101
|
+
if (toolIndex === -1) {
|
|
2102
|
+
outputError(`Tool '${toolName}' not found in general tools`, "TOOL_NOT_FOUND");
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
generalTools.splice(toolIndex, 1);
|
|
2106
|
+
await client.llm.update(source.llmId, { general_tools: generalTools });
|
|
2107
|
+
const output = {
|
|
2108
|
+
message: "Tool removed successfully (draft version)",
|
|
2109
|
+
agent_id: agentId,
|
|
2110
|
+
agent_name: source.agentName,
|
|
2111
|
+
tool_name: toolName,
|
|
2112
|
+
operation: "remove",
|
|
2113
|
+
location: { location: "general" },
|
|
2114
|
+
note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
|
|
2115
|
+
};
|
|
2116
|
+
outputJson(output);
|
|
2117
|
+
}
|
|
2118
|
+
} else {
|
|
2119
|
+
if (options.dryRun) {
|
|
2120
|
+
outputJson({
|
|
2121
|
+
message: "Dry run - no changes applied",
|
|
2122
|
+
agent_id: agentId,
|
|
2123
|
+
agent_name: source.agentName,
|
|
2124
|
+
tool_name: toolName,
|
|
2125
|
+
operation: "remove",
|
|
2126
|
+
location: existingTool.tool.location,
|
|
2127
|
+
tool_to_remove: existingTool.tool.tool
|
|
2128
|
+
});
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
2131
|
+
const flow = await client.conversationFlow.retrieve(source.flowId);
|
|
2132
|
+
if (existingTool.tool.location.location === "component") {
|
|
2133
|
+
const componentId = existingTool.tool.location.componentId;
|
|
2134
|
+
const components = flow.components ?? [];
|
|
2135
|
+
const compIndex = components.findIndex((c) => c.name === componentId);
|
|
2136
|
+
if (compIndex === -1) {
|
|
2137
|
+
outputError(`Component '${componentId}' not found`, "COMPONENT_NOT_FOUND");
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
2140
|
+
const updatedComponents = [...components];
|
|
2141
|
+
const compTools = [...updatedComponents[compIndex].tools ?? []];
|
|
2142
|
+
const toolIndex = compTools.findIndex((t) => t.name === toolName);
|
|
2143
|
+
if (toolIndex === -1) {
|
|
2144
|
+
outputError(`Tool '${toolName}' not found in component '${componentId}'`, "TOOL_NOT_FOUND");
|
|
2145
|
+
return;
|
|
2146
|
+
}
|
|
2147
|
+
compTools.splice(toolIndex, 1);
|
|
2148
|
+
updatedComponents[compIndex] = { ...updatedComponents[compIndex], tools: compTools };
|
|
2149
|
+
await client.conversationFlow.update(source.flowId, { components: updatedComponents });
|
|
2150
|
+
const output = {
|
|
2151
|
+
message: "Tool removed successfully (draft version)",
|
|
2152
|
+
agent_id: agentId,
|
|
2153
|
+
agent_name: source.agentName,
|
|
2154
|
+
tool_name: toolName,
|
|
2155
|
+
operation: "remove",
|
|
2156
|
+
location: { location: "component", componentId },
|
|
2157
|
+
note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
|
|
2158
|
+
};
|
|
2159
|
+
outputJson(output);
|
|
2160
|
+
} else {
|
|
2161
|
+
const flowTools = [...flow.tools ?? []];
|
|
2162
|
+
const toolIndex = flowTools.findIndex((t) => t.name === toolName);
|
|
2163
|
+
if (toolIndex === -1) {
|
|
2164
|
+
outputError(`Tool '${toolName}' not found in flow tools`, "TOOL_NOT_FOUND");
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
2167
|
+
flowTools.splice(toolIndex, 1);
|
|
2168
|
+
await client.conversationFlow.update(source.flowId, { tools: flowTools });
|
|
2169
|
+
const output = {
|
|
2170
|
+
message: "Tool removed successfully (draft version)",
|
|
2171
|
+
agent_id: agentId,
|
|
2172
|
+
agent_name: source.agentName,
|
|
2173
|
+
tool_name: toolName,
|
|
2174
|
+
operation: "remove",
|
|
2175
|
+
location: { location: "flow" },
|
|
2176
|
+
note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
|
|
2177
|
+
};
|
|
2178
|
+
outputJson(output);
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
} catch (error) {
|
|
2182
|
+
handleSdkError(error);
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
// src/commands/tools/export.ts
|
|
2187
|
+
var import_fs7 = require("fs");
|
|
2188
|
+
async function exportToolsCommand(agentId, options) {
|
|
2189
|
+
try {
|
|
2190
|
+
const source = await resolveToolsSource(agentId);
|
|
2191
|
+
if (source.type === "custom-llm") {
|
|
2192
|
+
outputError(source.error, "CUSTOM_LLM_NOT_SUPPORTED");
|
|
2193
|
+
return;
|
|
2194
|
+
}
|
|
2195
|
+
let exportData;
|
|
2196
|
+
if (source.type === "retell-llm") {
|
|
2197
|
+
const stateToolsExport = {};
|
|
2198
|
+
for (const [stateName, tools2] of Object.entries(source.stateTools)) {
|
|
2199
|
+
if (tools2.length > 0) {
|
|
2200
|
+
stateToolsExport[stateName] = tools2;
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
exportData = {
|
|
2204
|
+
agent_id: source.agentId,
|
|
2205
|
+
agent_name: source.agentName,
|
|
2206
|
+
engine_type: "retell-llm",
|
|
2207
|
+
exported_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2208
|
+
tools: {
|
|
2209
|
+
general: source.generalTools.length > 0 ? source.generalTools : void 0,
|
|
2210
|
+
states: Object.keys(stateToolsExport).length > 0 ? stateToolsExport : void 0
|
|
2211
|
+
},
|
|
2212
|
+
total_count: source.totalCount
|
|
2213
|
+
};
|
|
2214
|
+
} else {
|
|
2215
|
+
const componentToolsExport = {};
|
|
2216
|
+
for (const [compId, tools2] of Object.entries(source.componentTools)) {
|
|
2217
|
+
if (tools2.length > 0) {
|
|
2218
|
+
componentToolsExport[compId] = tools2;
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
exportData = {
|
|
2222
|
+
agent_id: source.agentId,
|
|
2223
|
+
agent_name: source.agentName,
|
|
2224
|
+
engine_type: "conversation-flow",
|
|
2225
|
+
exported_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2226
|
+
tools: {
|
|
2227
|
+
flow: source.flowTools.length > 0 ? source.flowTools : void 0,
|
|
2228
|
+
components: Object.keys(componentToolsExport).length > 0 ? componentToolsExport : void 0
|
|
2229
|
+
},
|
|
2230
|
+
total_count: source.totalCount
|
|
2231
|
+
};
|
|
2232
|
+
}
|
|
2233
|
+
if (options.output) {
|
|
2234
|
+
try {
|
|
2235
|
+
(0, import_fs7.writeFileSync)(options.output, JSON.stringify(exportData, null, 2), "utf-8");
|
|
2236
|
+
outputJson({
|
|
2237
|
+
message: "Tools exported successfully",
|
|
2238
|
+
agent_id: agentId,
|
|
2239
|
+
agent_name: exportData.agent_name,
|
|
2240
|
+
engine_type: exportData.engine_type,
|
|
2241
|
+
output_file: options.output,
|
|
2242
|
+
total_count: exportData.total_count
|
|
2243
|
+
});
|
|
2244
|
+
} catch (error) {
|
|
2245
|
+
if (error.code === "EACCES") {
|
|
2246
|
+
outputError(`Permission denied writing to: ${options.output}`, "PERMISSION_DENIED");
|
|
2247
|
+
} else if (error.code === "ENOSPC") {
|
|
2248
|
+
outputError("No space left on device", "NO_SPACE");
|
|
2249
|
+
} else {
|
|
2250
|
+
outputError(`Error writing file: ${error.message}`, "WRITE_ERROR");
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
} else {
|
|
2254
|
+
outputJson(exportData);
|
|
2255
|
+
}
|
|
2256
|
+
} catch (error) {
|
|
2257
|
+
handleSdkError(error);
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
// src/commands/tools/import.ts
|
|
2262
|
+
var import_fs8 = require("fs");
|
|
2263
|
+
async function importToolsCommand(agentId, options) {
|
|
2264
|
+
try {
|
|
2265
|
+
if (!(0, import_fs8.existsSync)(options.file)) {
|
|
2266
|
+
outputError(`Import file not found: ${options.file}`, "FILE_NOT_FOUND");
|
|
2267
|
+
return;
|
|
2268
|
+
}
|
|
2269
|
+
let importData;
|
|
2270
|
+
try {
|
|
2271
|
+
const content = (0, import_fs8.readFileSync)(options.file, "utf-8");
|
|
2272
|
+
importData = JSON.parse(content);
|
|
2273
|
+
} catch (error) {
|
|
2274
|
+
if (error instanceof SyntaxError) {
|
|
2275
|
+
outputError(`Invalid JSON in import file: ${error.message}`, "INVALID_JSON");
|
|
2276
|
+
} else {
|
|
2277
|
+
outputError(`Error reading import file: ${error.message}`, "FILE_READ_ERROR");
|
|
2278
|
+
}
|
|
2279
|
+
return;
|
|
2280
|
+
}
|
|
2281
|
+
if (!importData.tools) {
|
|
2282
|
+
outputError('Import file must contain a "tools" object', "INVALID_IMPORT");
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
const source = await resolveToolsSource(agentId);
|
|
2286
|
+
if (source.type === "custom-llm") {
|
|
2287
|
+
outputError(source.error, "CUSTOM_LLM_NOT_SUPPORTED");
|
|
2288
|
+
return;
|
|
2289
|
+
}
|
|
2290
|
+
const client = getRetellClient();
|
|
2291
|
+
const existingNames = new Set(getAllToolNames(source));
|
|
2292
|
+
const toolsAdded = [];
|
|
2293
|
+
const toolsSkipped = [];
|
|
2294
|
+
const toolsReplaced = [];
|
|
2295
|
+
if (source.type === "retell-llm") {
|
|
2296
|
+
if (importData.tools.flow || importData.tools.components) {
|
|
2297
|
+
outputError(
|
|
2298
|
+
"Import file contains Conversation Flow tools, but agent is Retell LLM",
|
|
2299
|
+
"TYPE_MISMATCH"
|
|
2300
|
+
);
|
|
2301
|
+
return;
|
|
2302
|
+
}
|
|
2303
|
+
const llm = await client.llm.retrieve(source.llmId);
|
|
2304
|
+
let generalTools = [...llm.general_tools ?? []];
|
|
2305
|
+
const states = [...llm.states ?? []];
|
|
2306
|
+
if (importData.tools.general) {
|
|
2307
|
+
for (const tool of importData.tools.general) {
|
|
2308
|
+
if (existingNames.has(tool.name)) {
|
|
2309
|
+
if (options.replace) {
|
|
2310
|
+
const idx = generalTools.findIndex((t) => t.name === tool.name);
|
|
2311
|
+
if (idx !== -1) {
|
|
2312
|
+
generalTools[idx] = tool;
|
|
2313
|
+
toolsReplaced.push(tool.name);
|
|
2314
|
+
}
|
|
2315
|
+
} else {
|
|
2316
|
+
toolsSkipped.push(tool.name);
|
|
2317
|
+
}
|
|
2318
|
+
} else {
|
|
2319
|
+
generalTools.push(tool);
|
|
2320
|
+
toolsAdded.push(tool.name);
|
|
2321
|
+
existingNames.add(tool.name);
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
if (importData.tools.states) {
|
|
2326
|
+
for (const [stateName, tools2] of Object.entries(importData.tools.states)) {
|
|
2327
|
+
const stateIndex = states.findIndex((s) => s.name === stateName);
|
|
2328
|
+
if (stateIndex === -1) {
|
|
2329
|
+
outputError(
|
|
2330
|
+
`State '${stateName}' not found in agent. Available states: ${states.map((s) => s.name).join(", ") || "none"}`,
|
|
2331
|
+
"STATE_NOT_FOUND"
|
|
2332
|
+
);
|
|
2333
|
+
return;
|
|
2334
|
+
}
|
|
2335
|
+
const stateTools = [...states[stateIndex].tools ?? []];
|
|
2336
|
+
for (const tool of tools2) {
|
|
2337
|
+
if (existingNames.has(tool.name)) {
|
|
2338
|
+
if (options.replace) {
|
|
2339
|
+
const idx = stateTools.findIndex((t) => t.name === tool.name);
|
|
2340
|
+
if (idx !== -1) {
|
|
2341
|
+
stateTools[idx] = tool;
|
|
2342
|
+
toolsReplaced.push(`${stateName}/${tool.name}`);
|
|
2343
|
+
}
|
|
2344
|
+
} else {
|
|
2345
|
+
toolsSkipped.push(`${stateName}/${tool.name}`);
|
|
2346
|
+
}
|
|
2347
|
+
} else {
|
|
2348
|
+
stateTools.push(tool);
|
|
2349
|
+
toolsAdded.push(`${stateName}/${tool.name}`);
|
|
2350
|
+
existingNames.add(tool.name);
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
states[stateIndex] = { ...states[stateIndex], tools: stateTools };
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
if (options.dryRun) {
|
|
2357
|
+
outputJson({
|
|
2358
|
+
message: "Dry run - no changes applied",
|
|
2359
|
+
agent_id: agentId,
|
|
2360
|
+
agent_name: source.agentName,
|
|
2361
|
+
engine_type: "retell-llm",
|
|
2362
|
+
tools_would_add: toolsAdded,
|
|
2363
|
+
tools_would_replace: toolsReplaced,
|
|
2364
|
+
tools_would_skip: toolsSkipped,
|
|
2365
|
+
total_changes: toolsAdded.length + toolsReplaced.length
|
|
2366
|
+
});
|
|
2367
|
+
return;
|
|
2368
|
+
}
|
|
2369
|
+
await client.llm.update(source.llmId, {
|
|
2370
|
+
general_tools: generalTools,
|
|
2371
|
+
states
|
|
2372
|
+
});
|
|
2373
|
+
const output = {
|
|
2374
|
+
message: "Tools imported successfully (draft version)",
|
|
2375
|
+
agent_id: agentId,
|
|
2376
|
+
agent_name: source.agentName,
|
|
2377
|
+
imported_count: toolsAdded.length + toolsReplaced.length,
|
|
2378
|
+
tools_added: [...toolsAdded, ...toolsReplaced],
|
|
2379
|
+
note: toolsSkipped.length > 0 ? `Skipped ${toolsSkipped.length} existing tools. Use --replace to overwrite. Run 'retell agent-publish ${agentId}' to publish changes.` : `Run 'retell agent-publish ${agentId}' to publish changes to production`
|
|
2380
|
+
};
|
|
2381
|
+
outputJson(output);
|
|
2382
|
+
} else {
|
|
2383
|
+
if (importData.tools.general || importData.tools.states) {
|
|
2384
|
+
outputError(
|
|
2385
|
+
"Import file contains Retell LLM tools, but agent is Conversation Flow",
|
|
2386
|
+
"TYPE_MISMATCH"
|
|
2387
|
+
);
|
|
2388
|
+
return;
|
|
2389
|
+
}
|
|
2390
|
+
const flow = await client.conversationFlow.retrieve(source.flowId);
|
|
2391
|
+
let flowTools = [...flow.tools ?? []];
|
|
2392
|
+
const components = [...flow.components ?? []];
|
|
2393
|
+
if (importData.tools.flow) {
|
|
2394
|
+
for (const tool of importData.tools.flow) {
|
|
2395
|
+
if (existingNames.has(tool.name)) {
|
|
2396
|
+
if (options.replace) {
|
|
2397
|
+
const idx = flowTools.findIndex((t) => t.name === tool.name);
|
|
2398
|
+
if (idx !== -1) {
|
|
2399
|
+
flowTools[idx] = tool;
|
|
2400
|
+
toolsReplaced.push(tool.name);
|
|
2401
|
+
}
|
|
2402
|
+
} else {
|
|
2403
|
+
toolsSkipped.push(tool.name);
|
|
2404
|
+
}
|
|
2405
|
+
} else {
|
|
2406
|
+
flowTools.push(tool);
|
|
2407
|
+
toolsAdded.push(tool.name);
|
|
2408
|
+
existingNames.add(tool.name);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
if (importData.tools.components) {
|
|
2413
|
+
for (const [compId, tools2] of Object.entries(importData.tools.components)) {
|
|
2414
|
+
const compIndex = components.findIndex((c) => c.name === compId);
|
|
2415
|
+
if (compIndex === -1) {
|
|
2416
|
+
outputError(
|
|
2417
|
+
`Component '${compId}' not found in agent. Available components: ${components.map((c) => c.name).join(", ") || "none"}`,
|
|
2418
|
+
"COMPONENT_NOT_FOUND"
|
|
2419
|
+
);
|
|
2420
|
+
return;
|
|
2421
|
+
}
|
|
2422
|
+
const compTools = [...components[compIndex].tools ?? []];
|
|
2423
|
+
for (const tool of tools2) {
|
|
2424
|
+
if (existingNames.has(tool.name)) {
|
|
2425
|
+
if (options.replace) {
|
|
2426
|
+
const idx = compTools.findIndex((t) => t.name === tool.name);
|
|
2427
|
+
if (idx !== -1) {
|
|
2428
|
+
compTools[idx] = tool;
|
|
2429
|
+
toolsReplaced.push(`${compId}/${tool.name}`);
|
|
2430
|
+
}
|
|
2431
|
+
} else {
|
|
2432
|
+
toolsSkipped.push(`${compId}/${tool.name}`);
|
|
2433
|
+
}
|
|
2434
|
+
} else {
|
|
2435
|
+
compTools.push(tool);
|
|
2436
|
+
toolsAdded.push(`${compId}/${tool.name}`);
|
|
2437
|
+
existingNames.add(tool.name);
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
components[compIndex] = { ...components[compIndex], tools: compTools };
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
if (options.dryRun) {
|
|
2444
|
+
outputJson({
|
|
2445
|
+
message: "Dry run - no changes applied",
|
|
2446
|
+
agent_id: agentId,
|
|
2447
|
+
agent_name: source.agentName,
|
|
2448
|
+
engine_type: "conversation-flow",
|
|
2449
|
+
tools_would_add: toolsAdded,
|
|
2450
|
+
tools_would_replace: toolsReplaced,
|
|
2451
|
+
tools_would_skip: toolsSkipped,
|
|
2452
|
+
total_changes: toolsAdded.length + toolsReplaced.length
|
|
2453
|
+
});
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
await client.conversationFlow.update(source.flowId, {
|
|
2457
|
+
tools: flowTools,
|
|
2458
|
+
components
|
|
2459
|
+
});
|
|
2460
|
+
const output = {
|
|
2461
|
+
message: "Tools imported successfully (draft version)",
|
|
2462
|
+
agent_id: agentId,
|
|
2463
|
+
agent_name: source.agentName,
|
|
2464
|
+
imported_count: toolsAdded.length + toolsReplaced.length,
|
|
2465
|
+
tools_added: [...toolsAdded, ...toolsReplaced],
|
|
2466
|
+
note: toolsSkipped.length > 0 ? `Skipped ${toolsSkipped.length} existing tools. Use --replace to overwrite. Run 'retell agent-publish ${agentId}' to publish changes.` : `Run 'retell agent-publish ${agentId}' to publish changes to production`
|
|
2467
|
+
};
|
|
2468
|
+
outputJson(output);
|
|
2469
|
+
}
|
|
2470
|
+
} catch (error) {
|
|
2471
|
+
if (error instanceof SyntaxError) {
|
|
2472
|
+
outputError(`Invalid JSON: ${error.message}`, "INVALID_JSON");
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
handleSdkError(error);
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
// src/services/test-api.ts
|
|
2480
|
+
var BASE_URL = "https://api.retellai.com";
|
|
2481
|
+
async function apiRequest(method, path, body) {
|
|
2482
|
+
const config = getConfig();
|
|
2483
|
+
const response = await fetch(`${BASE_URL}${path}`, {
|
|
2484
|
+
method,
|
|
2485
|
+
headers: {
|
|
2486
|
+
"Authorization": `Bearer ${config.apiKey}`,
|
|
2487
|
+
"Content-Type": "application/json"
|
|
2488
|
+
},
|
|
2489
|
+
body: body ? JSON.stringify(body) : void 0
|
|
2490
|
+
});
|
|
2491
|
+
if (!response.ok) {
|
|
2492
|
+
const errorBody = await response.text();
|
|
2493
|
+
let errorMessage = `API error: ${response.status} ${response.statusText}`;
|
|
2494
|
+
try {
|
|
2495
|
+
const errorJson = JSON.parse(errorBody);
|
|
2496
|
+
if (errorJson.message) {
|
|
2497
|
+
errorMessage = errorJson.message;
|
|
2498
|
+
} else if (errorJson.error) {
|
|
2499
|
+
errorMessage = errorJson.error;
|
|
2500
|
+
}
|
|
2501
|
+
} catch {
|
|
2502
|
+
if (errorBody) {
|
|
2503
|
+
errorMessage = errorBody;
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
throw new Error(errorMessage);
|
|
2507
|
+
}
|
|
2508
|
+
const text = await response.text();
|
|
2509
|
+
if (!text) {
|
|
2510
|
+
return {};
|
|
2511
|
+
}
|
|
2512
|
+
return JSON.parse(text);
|
|
2513
|
+
}
|
|
2514
|
+
async function listTestCaseDefinitions(responseEngine) {
|
|
2515
|
+
const params = new URLSearchParams();
|
|
2516
|
+
if (responseEngine.type === "retell-llm") {
|
|
2517
|
+
params.set("type", "retell-llm");
|
|
2518
|
+
params.set("llm_id", responseEngine.llm_id);
|
|
2519
|
+
} else {
|
|
2520
|
+
params.set("type", "conversation-flow");
|
|
2521
|
+
params.set("conversation_flow_id", responseEngine.conversation_flow_id);
|
|
2522
|
+
}
|
|
2523
|
+
return apiRequest(
|
|
2524
|
+
"GET",
|
|
2525
|
+
`/list-test-case-definitions?${params.toString()}`
|
|
2526
|
+
);
|
|
2527
|
+
}
|
|
2528
|
+
async function getTestCaseDefinition(testCaseDefinitionId) {
|
|
2529
|
+
return apiRequest(
|
|
2530
|
+
"GET",
|
|
2531
|
+
`/get-test-case-definition/${testCaseDefinitionId}`
|
|
2532
|
+
);
|
|
2533
|
+
}
|
|
2534
|
+
async function createTestCaseDefinition(params) {
|
|
2535
|
+
return apiRequest(
|
|
2536
|
+
"POST",
|
|
2537
|
+
"/create-test-case-definition",
|
|
2538
|
+
params
|
|
2539
|
+
);
|
|
2540
|
+
}
|
|
2541
|
+
async function updateTestCaseDefinition(testCaseDefinitionId, params) {
|
|
2542
|
+
return apiRequest(
|
|
2543
|
+
"PUT",
|
|
2544
|
+
`/update-test-case-definition/${testCaseDefinitionId}`,
|
|
2545
|
+
params
|
|
2546
|
+
);
|
|
2547
|
+
}
|
|
2548
|
+
async function deleteTestCaseDefinition(testCaseDefinitionId) {
|
|
2549
|
+
await apiRequest(
|
|
2550
|
+
"DELETE",
|
|
2551
|
+
`/delete-test-case-definition/${testCaseDefinitionId}`
|
|
2552
|
+
);
|
|
2553
|
+
}
|
|
2554
|
+
async function listBatchTests(responseEngine) {
|
|
2555
|
+
const params = new URLSearchParams();
|
|
2556
|
+
if (responseEngine.type === "retell-llm") {
|
|
2557
|
+
params.set("type", "retell-llm");
|
|
2558
|
+
params.set("llm_id", responseEngine.llm_id);
|
|
2559
|
+
} else {
|
|
2560
|
+
params.set("type", "conversation-flow");
|
|
2561
|
+
params.set("conversation_flow_id", responseEngine.conversation_flow_id);
|
|
2562
|
+
}
|
|
2563
|
+
return apiRequest(
|
|
2564
|
+
"GET",
|
|
2565
|
+
`/list-batch-tests?${params.toString()}`
|
|
2566
|
+
);
|
|
2567
|
+
}
|
|
2568
|
+
async function getBatchTest(batchJobId) {
|
|
2569
|
+
return apiRequest(
|
|
2570
|
+
"GET",
|
|
2571
|
+
`/get-batch-test/${batchJobId}`
|
|
2572
|
+
);
|
|
2573
|
+
}
|
|
2574
|
+
async function createBatchTest(params) {
|
|
2575
|
+
return apiRequest(
|
|
2576
|
+
"POST",
|
|
2577
|
+
"/create-batch-test",
|
|
2578
|
+
params
|
|
2579
|
+
);
|
|
2580
|
+
}
|
|
2581
|
+
async function listTestRuns(batchJobId) {
|
|
2582
|
+
return apiRequest(
|
|
2583
|
+
"GET",
|
|
2584
|
+
`/list-test-runs/${batchJobId}`
|
|
2585
|
+
);
|
|
2586
|
+
}
|
|
2587
|
+
async function getTestRun(testRunId) {
|
|
2588
|
+
return apiRequest(
|
|
2589
|
+
"GET",
|
|
2590
|
+
`/get-test-run/${testRunId}`
|
|
2591
|
+
);
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
// src/commands/tests/cases/list.ts
|
|
2595
|
+
function buildResponseEngine(options) {
|
|
2596
|
+
if (options.type === "retell-llm") {
|
|
2597
|
+
if (!options.llmId) {
|
|
2598
|
+
outputError("--llm-id is required when type is retell-llm", "MISSING_PARAMETER");
|
|
2599
|
+
return null;
|
|
2600
|
+
}
|
|
2601
|
+
return { type: "retell-llm", llm_id: options.llmId };
|
|
2602
|
+
} else {
|
|
2603
|
+
if (!options.flowId) {
|
|
2604
|
+
outputError("--flow-id is required when type is conversation-flow", "MISSING_PARAMETER");
|
|
2605
|
+
return null;
|
|
2606
|
+
}
|
|
2607
|
+
return { type: "conversation-flow", conversation_flow_id: options.flowId };
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
async function listTestCasesCommand(options) {
|
|
2611
|
+
try {
|
|
2612
|
+
const responseEngine = buildResponseEngine(options);
|
|
2613
|
+
if (!responseEngine)
|
|
2614
|
+
return;
|
|
2615
|
+
const testCases = await listTestCaseDefinitions(responseEngine);
|
|
2616
|
+
const output = {
|
|
2617
|
+
response_engine: responseEngine,
|
|
2618
|
+
test_case_definitions: testCases || [],
|
|
2619
|
+
total_count: (testCases || []).length
|
|
2620
|
+
};
|
|
2621
|
+
if (options.fields) {
|
|
2622
|
+
const filtered = filterFields(output, options.fields.split(",").map((f) => f.trim()));
|
|
2623
|
+
outputJson(filtered);
|
|
2624
|
+
} else {
|
|
2625
|
+
outputJson(output);
|
|
2626
|
+
}
|
|
2627
|
+
} catch (error) {
|
|
2628
|
+
handleSdkError(error);
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
// src/commands/tests/cases/get.ts
|
|
2633
|
+
async function getTestCaseCommand(testCaseDefinitionId, options) {
|
|
2634
|
+
try {
|
|
2635
|
+
const testCase = await getTestCaseDefinition(testCaseDefinitionId);
|
|
2636
|
+
if (options.fields) {
|
|
2637
|
+
const filtered = filterFields(testCase, options.fields.split(",").map((f) => f.trim()));
|
|
2638
|
+
outputJson(filtered);
|
|
2639
|
+
} else {
|
|
2640
|
+
outputJson(testCase);
|
|
2641
|
+
}
|
|
2642
|
+
} catch (error) {
|
|
2643
|
+
handleSdkError(error);
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
// src/commands/tests/cases/create.ts
|
|
2648
|
+
var import_fs9 = require("fs");
|
|
2649
|
+
function buildResponseEngine2(options) {
|
|
2650
|
+
if (options.llmId && options.flowId) {
|
|
2651
|
+
outputError("Cannot specify both --llm-id and --flow-id", "INVALID_PARAMETERS");
|
|
2652
|
+
return null;
|
|
2653
|
+
}
|
|
2654
|
+
if (!options.llmId && !options.flowId) {
|
|
2655
|
+
outputError("Either --llm-id or --flow-id is required", "MISSING_PARAMETER");
|
|
2656
|
+
return null;
|
|
2657
|
+
}
|
|
2658
|
+
if (options.llmId) {
|
|
2659
|
+
const engine = { type: "retell-llm", llm_id: options.llmId };
|
|
2660
|
+
if (options.version !== void 0) {
|
|
2661
|
+
engine.version = options.version;
|
|
2662
|
+
}
|
|
2663
|
+
return engine;
|
|
2664
|
+
} else {
|
|
2665
|
+
const engine = { type: "conversation-flow", conversation_flow_id: options.flowId };
|
|
2666
|
+
if (options.version !== void 0) {
|
|
2667
|
+
engine.version = options.version;
|
|
2668
|
+
}
|
|
2669
|
+
return engine;
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
async function createTestCaseCommand(options) {
|
|
2673
|
+
try {
|
|
2674
|
+
if (!(0, import_fs9.existsSync)(options.file)) {
|
|
2675
|
+
outputError(`Test case file not found: ${options.file}`, "FILE_NOT_FOUND");
|
|
2676
|
+
return;
|
|
2677
|
+
}
|
|
2678
|
+
let input;
|
|
2679
|
+
try {
|
|
2680
|
+
const content = (0, import_fs9.readFileSync)(options.file, "utf-8");
|
|
2681
|
+
input = JSON.parse(content);
|
|
2682
|
+
} catch (error) {
|
|
2683
|
+
if (error instanceof SyntaxError) {
|
|
2684
|
+
outputError(`Invalid JSON in test case file: ${error.message}`, "INVALID_JSON");
|
|
2685
|
+
} else {
|
|
2686
|
+
outputError(`Error reading test case file: ${error.message}`, "FILE_READ_ERROR");
|
|
2687
|
+
}
|
|
2688
|
+
return;
|
|
2689
|
+
}
|
|
2690
|
+
if (!input.name) {
|
|
2691
|
+
outputError('Test case must have a "name" field', "INVALID_INPUT");
|
|
2692
|
+
return;
|
|
2693
|
+
}
|
|
2694
|
+
const responseEngine = buildResponseEngine2(options);
|
|
2695
|
+
if (!responseEngine)
|
|
2696
|
+
return;
|
|
2697
|
+
const testCase = await createTestCaseDefinition({
|
|
2698
|
+
name: input.name,
|
|
2699
|
+
user_prompt: input.user_prompt,
|
|
2700
|
+
scenario: input.scenario,
|
|
2701
|
+
metrics: input.metrics,
|
|
2702
|
+
response_engine: responseEngine
|
|
2703
|
+
});
|
|
2704
|
+
const output = {
|
|
2705
|
+
message: "Test case definition created successfully",
|
|
2706
|
+
test_case_definition_id: testCase.test_case_definition_id,
|
|
2707
|
+
name: testCase.name,
|
|
2708
|
+
operation: "create",
|
|
2709
|
+
response_engine: responseEngine
|
|
2710
|
+
};
|
|
2711
|
+
outputJson(output);
|
|
2712
|
+
} catch (error) {
|
|
2713
|
+
if (error instanceof SyntaxError) {
|
|
2714
|
+
outputError(`Invalid JSON: ${error.message}`, "INVALID_JSON");
|
|
2715
|
+
return;
|
|
2716
|
+
}
|
|
2717
|
+
handleSdkError(error);
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
// src/commands/tests/cases/update.ts
|
|
2722
|
+
var import_fs10 = require("fs");
|
|
2723
|
+
async function updateTestCaseCommand(testCaseDefinitionId, options) {
|
|
2724
|
+
try {
|
|
2725
|
+
if (!(0, import_fs10.existsSync)(options.file)) {
|
|
2726
|
+
outputError(`Test case file not found: ${options.file}`, "FILE_NOT_FOUND");
|
|
2727
|
+
return;
|
|
2728
|
+
}
|
|
2729
|
+
let input;
|
|
2730
|
+
try {
|
|
2731
|
+
const content = (0, import_fs10.readFileSync)(options.file, "utf-8");
|
|
2732
|
+
input = JSON.parse(content);
|
|
2733
|
+
} catch (error) {
|
|
2734
|
+
if (error instanceof SyntaxError) {
|
|
2735
|
+
outputError(`Invalid JSON in test case file: ${error.message}`, "INVALID_JSON");
|
|
2736
|
+
} else {
|
|
2737
|
+
outputError(`Error reading test case file: ${error.message}`, "FILE_READ_ERROR");
|
|
2738
|
+
}
|
|
2739
|
+
return;
|
|
2740
|
+
}
|
|
2741
|
+
const testCase = await updateTestCaseDefinition(testCaseDefinitionId, {
|
|
2742
|
+
name: input.name,
|
|
2743
|
+
user_prompt: input.user_prompt,
|
|
2744
|
+
scenario: input.scenario,
|
|
2745
|
+
metrics: input.metrics
|
|
2746
|
+
});
|
|
2747
|
+
const output = {
|
|
2748
|
+
message: "Test case definition updated successfully",
|
|
2749
|
+
test_case_definition_id: testCase.test_case_definition_id,
|
|
2750
|
+
name: testCase.name,
|
|
2751
|
+
operation: "update",
|
|
2752
|
+
response_engine: testCase.response_engine
|
|
2753
|
+
};
|
|
2754
|
+
outputJson(output);
|
|
2755
|
+
} catch (error) {
|
|
2756
|
+
if (error instanceof SyntaxError) {
|
|
2757
|
+
outputError(`Invalid JSON: ${error.message}`, "INVALID_JSON");
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2760
|
+
handleSdkError(error);
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
// src/commands/tests/cases/delete.ts
|
|
2765
|
+
async function deleteTestCaseCommand(testCaseDefinitionId) {
|
|
2766
|
+
try {
|
|
2767
|
+
await deleteTestCaseDefinition(testCaseDefinitionId);
|
|
2768
|
+
outputJson({
|
|
2769
|
+
message: "Test case definition deleted successfully",
|
|
2770
|
+
test_case_definition_id: testCaseDefinitionId,
|
|
2771
|
+
operation: "delete"
|
|
2772
|
+
});
|
|
2773
|
+
} catch (error) {
|
|
2774
|
+
handleSdkError(error);
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
// src/commands/tests/batch/list.ts
|
|
2779
|
+
function buildResponseEngine3(options) {
|
|
2780
|
+
if (options.type === "retell-llm") {
|
|
2781
|
+
if (!options.llmId) {
|
|
2782
|
+
outputError("--llm-id is required when type is retell-llm", "MISSING_PARAMETER");
|
|
2783
|
+
return null;
|
|
2784
|
+
}
|
|
2785
|
+
return { type: "retell-llm", llm_id: options.llmId };
|
|
2786
|
+
} else {
|
|
2787
|
+
if (!options.flowId) {
|
|
2788
|
+
outputError("--flow-id is required when type is conversation-flow", "MISSING_PARAMETER");
|
|
2789
|
+
return null;
|
|
2790
|
+
}
|
|
2791
|
+
return { type: "conversation-flow", conversation_flow_id: options.flowId };
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
async function listBatchTestsCommand(options) {
|
|
2795
|
+
try {
|
|
2796
|
+
const responseEngine = buildResponseEngine3(options);
|
|
2797
|
+
if (!responseEngine)
|
|
2798
|
+
return;
|
|
2799
|
+
const batchTests = await listBatchTests(responseEngine);
|
|
2800
|
+
const output = {
|
|
2801
|
+
response_engine: responseEngine,
|
|
2802
|
+
batch_tests: batchTests || [],
|
|
2803
|
+
total_count: (batchTests || []).length
|
|
2804
|
+
};
|
|
2805
|
+
if (options.fields) {
|
|
2806
|
+
const filtered = filterFields(output, options.fields.split(",").map((f) => f.trim()));
|
|
2807
|
+
outputJson(filtered);
|
|
2808
|
+
} else {
|
|
2809
|
+
outputJson(output);
|
|
2810
|
+
}
|
|
2811
|
+
} catch (error) {
|
|
2812
|
+
handleSdkError(error);
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
|
|
2816
|
+
// src/commands/tests/batch/get.ts
|
|
2817
|
+
async function getBatchTestCommand(batchJobId, options) {
|
|
2818
|
+
try {
|
|
2819
|
+
const batchTest = await getBatchTest(batchJobId);
|
|
2820
|
+
if (options.fields) {
|
|
2821
|
+
const filtered = filterFields(batchTest, options.fields.split(",").map((f) => f.trim()));
|
|
2822
|
+
outputJson(filtered);
|
|
2823
|
+
} else {
|
|
2824
|
+
outputJson(batchTest);
|
|
2825
|
+
}
|
|
2826
|
+
} catch (error) {
|
|
2827
|
+
handleSdkError(error);
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2831
|
+
// src/commands/tests/batch/create.ts
|
|
2832
|
+
function buildResponseEngine4(options) {
|
|
2833
|
+
if (options.llmId && options.flowId) {
|
|
2834
|
+
outputError("Cannot specify both --llm-id and --flow-id", "INVALID_PARAMETERS");
|
|
2835
|
+
return null;
|
|
2836
|
+
}
|
|
2837
|
+
if (!options.llmId && !options.flowId) {
|
|
2838
|
+
outputError("Either --llm-id or --flow-id is required", "MISSING_PARAMETER");
|
|
2839
|
+
return null;
|
|
2840
|
+
}
|
|
2841
|
+
if (options.llmId) {
|
|
2842
|
+
const engine = { type: "retell-llm", llm_id: options.llmId };
|
|
2843
|
+
if (options.version !== void 0) {
|
|
2844
|
+
engine.version = options.version;
|
|
2845
|
+
}
|
|
2846
|
+
return engine;
|
|
2847
|
+
} else {
|
|
2848
|
+
const engine = { type: "conversation-flow", conversation_flow_id: options.flowId };
|
|
2849
|
+
if (options.version !== void 0) {
|
|
2850
|
+
engine.version = options.version;
|
|
2851
|
+
}
|
|
2852
|
+
return engine;
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
async function createBatchTestCommand(options) {
|
|
2856
|
+
try {
|
|
2857
|
+
const responseEngine = buildResponseEngine4(options);
|
|
2858
|
+
if (!responseEngine)
|
|
2859
|
+
return;
|
|
2860
|
+
const testCaseDefinitionIds = options.cases.split(",").map((id) => id.trim()).filter(Boolean);
|
|
2861
|
+
if (testCaseDefinitionIds.length === 0) {
|
|
2862
|
+
outputError("At least one test case definition ID is required", "MISSING_PARAMETER");
|
|
2863
|
+
return;
|
|
2864
|
+
}
|
|
2865
|
+
const batchTest = await createBatchTest({
|
|
2866
|
+
response_engine: responseEngine,
|
|
2867
|
+
test_case_definition_ids: testCaseDefinitionIds
|
|
2868
|
+
});
|
|
2869
|
+
const output = {
|
|
2870
|
+
message: "Batch test created successfully",
|
|
2871
|
+
test_case_batch_job_id: batchTest.test_case_batch_job_id,
|
|
2872
|
+
status: batchTest.status,
|
|
2873
|
+
response_engine: responseEngine,
|
|
2874
|
+
test_case_definition_ids: testCaseDefinitionIds
|
|
2875
|
+
};
|
|
2876
|
+
outputJson(output);
|
|
2877
|
+
} catch (error) {
|
|
2878
|
+
handleSdkError(error);
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
// src/commands/tests/runs/list.ts
|
|
2883
|
+
async function listTestRunsCommand(batchJobId, options) {
|
|
2884
|
+
try {
|
|
2885
|
+
const testRuns = await listTestRuns(batchJobId);
|
|
2886
|
+
const output = {
|
|
2887
|
+
batch_job_id: batchJobId,
|
|
2888
|
+
test_runs: testRuns || [],
|
|
2889
|
+
total_count: (testRuns || []).length
|
|
2890
|
+
};
|
|
2891
|
+
if (options.fields) {
|
|
2892
|
+
const filtered = filterFields(output, options.fields.split(",").map((f) => f.trim()));
|
|
2893
|
+
outputJson(filtered);
|
|
2894
|
+
} else {
|
|
2895
|
+
outputJson(output);
|
|
2896
|
+
}
|
|
2897
|
+
} catch (error) {
|
|
2898
|
+
handleSdkError(error);
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
// src/commands/tests/runs/get.ts
|
|
2903
|
+
async function getTestRunCommand(testRunId, options) {
|
|
2904
|
+
try {
|
|
2905
|
+
const testRun = await getTestRun(testRunId);
|
|
2906
|
+
if (options.fields) {
|
|
2907
|
+
const filtered = filterFields(testRun, options.fields.split(",").map((f) => f.trim()));
|
|
2908
|
+
outputJson(filtered);
|
|
2909
|
+
} else {
|
|
2910
|
+
outputJson(testRun);
|
|
2911
|
+
}
|
|
2912
|
+
} catch (error) {
|
|
2913
|
+
handleSdkError(error);
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2917
|
+
// src/commands/kb/list.ts
|
|
2918
|
+
async function listKnowledgeBasesCommand(options) {
|
|
2919
|
+
try {
|
|
2920
|
+
const client = getRetellClient();
|
|
2921
|
+
const knowledgeBases = await client.knowledgeBase.list();
|
|
2922
|
+
const output = options.fields ? filterFields(knowledgeBases, options.fields.split(",").map((f) => f.trim())) : knowledgeBases;
|
|
2923
|
+
outputJson(output);
|
|
2924
|
+
} catch (error) {
|
|
2925
|
+
handleSdkError(error);
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
// src/commands/kb/get.ts
|
|
2930
|
+
async function getKnowledgeBaseCommand(knowledgeBaseId, options) {
|
|
2931
|
+
try {
|
|
2932
|
+
const client = getRetellClient();
|
|
2933
|
+
const knowledgeBase = await client.knowledgeBase.retrieve(knowledgeBaseId);
|
|
2934
|
+
const output = options.fields ? filterFields(knowledgeBase, options.fields.split(",").map((f) => f.trim())) : knowledgeBase;
|
|
2935
|
+
outputJson(output);
|
|
2936
|
+
} catch (error) {
|
|
2937
|
+
handleSdkError(error);
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
// src/commands/kb/create.ts
|
|
2942
|
+
var import_fs11 = require("fs");
|
|
2943
|
+
async function createKnowledgeBaseCommand(options) {
|
|
2944
|
+
try {
|
|
2945
|
+
if (options.name.length > 40) {
|
|
2946
|
+
outputError("Knowledge base name must be 40 characters or less", "INVALID_NAME");
|
|
2947
|
+
return;
|
|
2948
|
+
}
|
|
2949
|
+
const createParams = {
|
|
2950
|
+
knowledge_base_name: options.name
|
|
2951
|
+
};
|
|
2952
|
+
if (options.autoRefresh) {
|
|
2953
|
+
createParams.enable_auto_refresh = true;
|
|
2954
|
+
}
|
|
2955
|
+
if (options.urls) {
|
|
2956
|
+
const urls = options.urls.split(",").map((u) => u.trim()).filter((u) => u.length > 0);
|
|
2957
|
+
if (urls.length > 0) {
|
|
2958
|
+
createParams.knowledge_base_urls = urls;
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
if (options.texts) {
|
|
2962
|
+
if (!(0, import_fs11.existsSync)(options.texts)) {
|
|
2963
|
+
outputError(`Texts file not found: ${options.texts}`, "FILE_NOT_FOUND");
|
|
2964
|
+
return;
|
|
2965
|
+
}
|
|
2966
|
+
try {
|
|
2967
|
+
const content = (0, import_fs11.readFileSync)(options.texts, "utf-8");
|
|
2968
|
+
const textsData = JSON.parse(content);
|
|
2969
|
+
if (!Array.isArray(textsData)) {
|
|
2970
|
+
outputError("Texts file must contain an array of { title, text } objects", "INVALID_TEXTS");
|
|
2971
|
+
return;
|
|
2972
|
+
}
|
|
2973
|
+
const texts = textsData.map((entry, index) => {
|
|
2974
|
+
if (typeof entry !== "object" || entry === null) {
|
|
2975
|
+
throw new Error(`Entry at index ${index} must be an object`);
|
|
2976
|
+
}
|
|
2977
|
+
const e = entry;
|
|
2978
|
+
if (typeof e.title !== "string" || typeof e.text !== "string") {
|
|
2979
|
+
throw new Error(`Entry at index ${index} must have "title" and "text" string fields`);
|
|
2980
|
+
}
|
|
2981
|
+
return { title: e.title, text: e.text };
|
|
2982
|
+
});
|
|
2983
|
+
if (texts.length > 0) {
|
|
2984
|
+
createParams.knowledge_base_texts = texts;
|
|
2985
|
+
}
|
|
2986
|
+
} catch (error) {
|
|
2987
|
+
if (error instanceof SyntaxError) {
|
|
2988
|
+
outputError(`Invalid JSON in texts file: ${error.message}`, "INVALID_JSON");
|
|
2989
|
+
} else if (error instanceof Error) {
|
|
2990
|
+
outputError(`Error parsing texts file: ${error.message}`, "INVALID_TEXTS");
|
|
2991
|
+
}
|
|
2992
|
+
return;
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
const client = getRetellClient();
|
|
2996
|
+
const knowledgeBase = await client.knowledgeBase.create(createParams);
|
|
2997
|
+
const output = {
|
|
2998
|
+
message: "Knowledge base created successfully",
|
|
2999
|
+
knowledge_base_id: knowledgeBase.knowledge_base_id,
|
|
3000
|
+
knowledge_base_name: knowledgeBase.knowledge_base_name,
|
|
3001
|
+
operation: "create"
|
|
3002
|
+
};
|
|
3003
|
+
outputJson({
|
|
3004
|
+
...output,
|
|
3005
|
+
status: knowledgeBase.status,
|
|
3006
|
+
sources_count: knowledgeBase.knowledge_base_sources?.length ?? 0
|
|
3007
|
+
});
|
|
3008
|
+
} catch (error) {
|
|
3009
|
+
handleSdkError(error);
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
|
|
3013
|
+
// src/commands/kb/delete.ts
|
|
3014
|
+
async function deleteKnowledgeBaseCommand(knowledgeBaseId) {
|
|
3015
|
+
try {
|
|
3016
|
+
const client = getRetellClient();
|
|
3017
|
+
await client.knowledgeBase.delete(knowledgeBaseId);
|
|
3018
|
+
const output = {
|
|
3019
|
+
message: "Knowledge base deleted successfully",
|
|
3020
|
+
knowledge_base_id: knowledgeBaseId,
|
|
3021
|
+
operation: "delete"
|
|
3022
|
+
};
|
|
3023
|
+
outputJson(output);
|
|
3024
|
+
} catch (error) {
|
|
3025
|
+
handleSdkError(error);
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3029
|
+
// src/commands/kb/sources/add.ts
|
|
3030
|
+
var import_fs12 = require("fs");
|
|
3031
|
+
async function addKnowledgeBaseSourcesCommand(knowledgeBaseId, options) {
|
|
3032
|
+
try {
|
|
3033
|
+
if (!options.urls && !options.texts) {
|
|
3034
|
+
outputError("At least one of --urls or --texts must be provided", "MISSING_SOURCES");
|
|
3035
|
+
return;
|
|
3036
|
+
}
|
|
3037
|
+
const addParams = {};
|
|
3038
|
+
if (options.urls) {
|
|
3039
|
+
const urls = options.urls.split(",").map((u) => u.trim()).filter((u) => u.length > 0);
|
|
3040
|
+
if (urls.length > 0) {
|
|
3041
|
+
addParams.knowledge_base_urls = urls;
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
if (options.texts) {
|
|
3045
|
+
if (!(0, import_fs12.existsSync)(options.texts)) {
|
|
3046
|
+
outputError(`Texts file not found: ${options.texts}`, "FILE_NOT_FOUND");
|
|
3047
|
+
return;
|
|
3048
|
+
}
|
|
3049
|
+
try {
|
|
3050
|
+
const content = (0, import_fs12.readFileSync)(options.texts, "utf-8");
|
|
3051
|
+
const textsData = JSON.parse(content);
|
|
3052
|
+
if (!Array.isArray(textsData)) {
|
|
3053
|
+
outputError("Texts file must contain an array of { title, text } objects", "INVALID_TEXTS");
|
|
3054
|
+
return;
|
|
3055
|
+
}
|
|
3056
|
+
const texts = textsData.map((entry, index) => {
|
|
3057
|
+
if (typeof entry !== "object" || entry === null) {
|
|
3058
|
+
throw new Error(`Entry at index ${index} must be an object`);
|
|
3059
|
+
}
|
|
3060
|
+
const e = entry;
|
|
3061
|
+
if (typeof e.title !== "string" || typeof e.text !== "string") {
|
|
3062
|
+
throw new Error(`Entry at index ${index} must have "title" and "text" string fields`);
|
|
3063
|
+
}
|
|
3064
|
+
return { title: e.title, text: e.text };
|
|
3065
|
+
});
|
|
3066
|
+
if (texts.length > 0) {
|
|
3067
|
+
addParams.knowledge_base_texts = texts;
|
|
3068
|
+
}
|
|
3069
|
+
} catch (error) {
|
|
3070
|
+
if (error instanceof SyntaxError) {
|
|
3071
|
+
outputError(`Invalid JSON in texts file: ${error.message}`, "INVALID_JSON");
|
|
3072
|
+
} else if (error instanceof Error) {
|
|
3073
|
+
outputError(`Error parsing texts file: ${error.message}`, "INVALID_TEXTS");
|
|
3074
|
+
}
|
|
3075
|
+
return;
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
const client = getRetellClient();
|
|
3079
|
+
const knowledgeBase = await client.knowledgeBase.addSources(knowledgeBaseId, addParams);
|
|
3080
|
+
const output = {
|
|
3081
|
+
message: "Sources added successfully",
|
|
3082
|
+
knowledge_base_id: knowledgeBase.knowledge_base_id,
|
|
3083
|
+
knowledge_base_name: knowledgeBase.knowledge_base_name,
|
|
3084
|
+
operation: "add_sources"
|
|
3085
|
+
};
|
|
3086
|
+
outputJson({
|
|
3087
|
+
...output,
|
|
3088
|
+
status: knowledgeBase.status,
|
|
3089
|
+
sources_count: knowledgeBase.knowledge_base_sources?.length ?? 0
|
|
3090
|
+
});
|
|
3091
|
+
} catch (error) {
|
|
3092
|
+
handleSdkError(error);
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
|
|
3096
|
+
// src/commands/kb/sources/delete.ts
|
|
3097
|
+
async function deleteKnowledgeBaseSourceCommand(knowledgeBaseId, sourceId) {
|
|
3098
|
+
try {
|
|
3099
|
+
const client = getRetellClient();
|
|
3100
|
+
const knowledgeBase = await client.knowledgeBase.deleteSource(knowledgeBaseId, sourceId);
|
|
3101
|
+
const output = {
|
|
3102
|
+
message: "Source deleted successfully",
|
|
3103
|
+
knowledge_base_id: knowledgeBase.knowledge_base_id,
|
|
3104
|
+
knowledge_base_name: knowledgeBase.knowledge_base_name,
|
|
3105
|
+
operation: "delete_source"
|
|
3106
|
+
};
|
|
3107
|
+
outputJson({
|
|
3108
|
+
...output,
|
|
3109
|
+
status: knowledgeBase.status,
|
|
3110
|
+
sources_count: knowledgeBase.knowledge_base_sources?.length ?? 0
|
|
3111
|
+
});
|
|
3112
|
+
} catch (error) {
|
|
3113
|
+
handleSdkError(error);
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
// src/commands/flows/list.ts
|
|
3118
|
+
async function listFlowsCommand(options) {
|
|
3119
|
+
try {
|
|
3120
|
+
const client = getRetellClient();
|
|
3121
|
+
const flows2 = await client.conversationFlow.list({
|
|
3122
|
+
limit: options.limit || 100
|
|
3123
|
+
});
|
|
3124
|
+
const output = options.fields ? filterFields(flows2, options.fields.split(",").map((f) => f.trim())) : flows2;
|
|
3125
|
+
outputJson(output);
|
|
3126
|
+
} catch (error) {
|
|
3127
|
+
handleSdkError(error);
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
|
|
3131
|
+
// src/commands/flows/get.ts
|
|
3132
|
+
async function getFlowCommand(conversationFlowId, options) {
|
|
3133
|
+
try {
|
|
3134
|
+
const client = getRetellClient();
|
|
3135
|
+
const retrieveOptions = {};
|
|
3136
|
+
if (options.version !== void 0) {
|
|
3137
|
+
retrieveOptions.version = options.version;
|
|
3138
|
+
}
|
|
3139
|
+
const flow = await client.conversationFlow.retrieve(conversationFlowId, retrieveOptions);
|
|
3140
|
+
const output = options.fields ? filterFields(flow, options.fields.split(",").map((f) => f.trim())) : flow;
|
|
3141
|
+
outputJson(output);
|
|
3142
|
+
} catch (error) {
|
|
3143
|
+
handleSdkError(error);
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
|
|
3147
|
+
// src/commands/flows/create.ts
|
|
3148
|
+
var import_fs13 = require("fs");
|
|
3149
|
+
async function createFlowCommand(options) {
|
|
3150
|
+
try {
|
|
3151
|
+
if (!(0, import_fs13.existsSync)(options.file)) {
|
|
3152
|
+
outputError(`Flow file not found: ${options.file}`, "FILE_NOT_FOUND");
|
|
3153
|
+
return;
|
|
3154
|
+
}
|
|
3155
|
+
let flowConfig;
|
|
3156
|
+
try {
|
|
3157
|
+
const content = (0, import_fs13.readFileSync)(options.file, "utf-8");
|
|
3158
|
+
flowConfig = JSON.parse(content);
|
|
3159
|
+
} catch (error) {
|
|
3160
|
+
if (error instanceof SyntaxError) {
|
|
3161
|
+
outputError(`Invalid JSON in flow file: ${error.message}`, "INVALID_JSON");
|
|
3162
|
+
} else if (error instanceof Error) {
|
|
3163
|
+
outputError(`Error reading flow file: ${error.message}`, "FILE_READ_ERROR");
|
|
3164
|
+
}
|
|
3165
|
+
return;
|
|
3166
|
+
}
|
|
3167
|
+
if (!flowConfig.start_speaker) {
|
|
3168
|
+
outputError('Flow configuration must have a "start_speaker" field (user or agent)', "INVALID_FLOW");
|
|
3169
|
+
return;
|
|
3170
|
+
}
|
|
3171
|
+
const client = getRetellClient();
|
|
3172
|
+
const flow = await client.conversationFlow.create(flowConfig);
|
|
3173
|
+
const output = {
|
|
3174
|
+
message: "Conversation flow created successfully",
|
|
3175
|
+
conversation_flow_id: flow.conversation_flow_id,
|
|
3176
|
+
version: flow.version,
|
|
3177
|
+
operation: "create"
|
|
3178
|
+
};
|
|
3179
|
+
outputJson(output);
|
|
3180
|
+
} catch (error) {
|
|
3181
|
+
handleSdkError(error);
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3185
|
+
// src/commands/flows/update.ts
|
|
3186
|
+
var import_fs14 = require("fs");
|
|
3187
|
+
async function updateFlowCommand(conversationFlowId, options) {
|
|
3188
|
+
try {
|
|
3189
|
+
if (!(0, import_fs14.existsSync)(options.file)) {
|
|
3190
|
+
outputError(`Flow file not found: ${options.file}`, "FILE_NOT_FOUND");
|
|
3191
|
+
return;
|
|
3192
|
+
}
|
|
3193
|
+
let flowUpdates;
|
|
3194
|
+
try {
|
|
3195
|
+
const content = (0, import_fs14.readFileSync)(options.file, "utf-8");
|
|
3196
|
+
flowUpdates = JSON.parse(content);
|
|
3197
|
+
} catch (error) {
|
|
3198
|
+
if (error instanceof SyntaxError) {
|
|
3199
|
+
outputError(`Invalid JSON in flow file: ${error.message}`, "INVALID_JSON");
|
|
3200
|
+
} else if (error instanceof Error) {
|
|
3201
|
+
outputError(`Error reading flow file: ${error.message}`, "FILE_READ_ERROR");
|
|
3202
|
+
}
|
|
3203
|
+
return;
|
|
3204
|
+
}
|
|
3205
|
+
if (options.version !== void 0) {
|
|
3206
|
+
flowUpdates.version = options.version;
|
|
3207
|
+
}
|
|
3208
|
+
const client = getRetellClient();
|
|
3209
|
+
const flow = await client.conversationFlow.update(conversationFlowId, flowUpdates);
|
|
3210
|
+
const output = {
|
|
3211
|
+
message: "Conversation flow updated successfully",
|
|
3212
|
+
conversation_flow_id: flow.conversation_flow_id,
|
|
3213
|
+
version: flow.version,
|
|
3214
|
+
operation: "update"
|
|
3215
|
+
};
|
|
3216
|
+
outputJson(output);
|
|
3217
|
+
} catch (error) {
|
|
3218
|
+
handleSdkError(error);
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3222
|
+
// src/commands/flows/delete.ts
|
|
3223
|
+
async function deleteFlowCommand(conversationFlowId) {
|
|
3224
|
+
try {
|
|
3225
|
+
const client = getRetellClient();
|
|
3226
|
+
await client.conversationFlow.delete(conversationFlowId);
|
|
3227
|
+
const output = {
|
|
3228
|
+
message: "Conversation flow deleted successfully",
|
|
3229
|
+
conversation_flow_id: conversationFlowId,
|
|
3230
|
+
operation: "delete"
|
|
3231
|
+
};
|
|
3232
|
+
outputJson(output);
|
|
3233
|
+
} catch (error) {
|
|
3234
|
+
handleSdkError(error);
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
|
|
3238
|
+
// src/index.ts
|
|
3239
|
+
var packageJson = JSON.parse(
|
|
3240
|
+
(0, import_fs15.readFileSync)((0, import_path6.join)(__dirname, "../package.json"), "utf-8")
|
|
3241
|
+
);
|
|
3242
|
+
var program = new import_commander.Command();
|
|
3243
|
+
program.name("retell").description("Retell AI CLI - Manage transcripts and agent prompts").version(packageJson.version, "-v, --version", "Display version number").helpOption("-h, --help", "Display help for command").option("--json", "Output as JSON (default)", true);
|
|
3244
|
+
program.command("login").description("Authenticate with Retell AI").addHelpText("after", `
|
|
3245
|
+
Examples:
|
|
3246
|
+
$ retell login
|
|
3247
|
+
# Enter your API key when prompted
|
|
3248
|
+
# Creates .retellrc.json in current directory
|
|
3249
|
+
`).action(async () => {
|
|
3250
|
+
await loginCommand();
|
|
3251
|
+
});
|
|
3252
|
+
var transcripts = program.command("transcripts").description("Manage call transcripts");
|
|
3253
|
+
transcripts.command("list").description("List all call transcripts").option("-l, --limit <number>", "Maximum number of calls to return (default: 50)", "50").option("--fields <fields>", "Comma-separated list of fields to return (e.g., call_id,call_status,metadata.duration)").addHelpText("after", `
|
|
3254
|
+
Examples:
|
|
3255
|
+
$ retell transcripts list
|
|
3256
|
+
$ retell transcripts list --limit 100
|
|
3257
|
+
$ retell transcripts list --fields call_id,call_status
|
|
3258
|
+
$ retell transcripts list | jq '.[] | select(.call_status == "error")'
|
|
3259
|
+
`).action(async (options) => {
|
|
3260
|
+
const limit = parseInt(options.limit, 10);
|
|
3261
|
+
if (isNaN(limit) || limit < 1) {
|
|
3262
|
+
console.error("Error: limit must be a positive number");
|
|
3263
|
+
process.exit(1);
|
|
3264
|
+
}
|
|
3265
|
+
await listTranscriptsCommand({
|
|
3266
|
+
limit,
|
|
3267
|
+
fields: options.fields
|
|
3268
|
+
});
|
|
3269
|
+
});
|
|
3270
|
+
transcripts.command("get <call_id>").description("Get a specific call transcript").option("--fields <fields>", "Comma-separated list of fields to return (e.g., call_id,metadata.duration,analysis)").addHelpText("after", `
|
|
3271
|
+
Examples:
|
|
3272
|
+
$ retell transcripts get call_abc123
|
|
3273
|
+
$ retell transcripts get call_abc123 --fields call_id,metadata.duration
|
|
3274
|
+
$ retell transcripts get call_abc123 | jq '.transcript_object'
|
|
3275
|
+
`).action(async (callId, options) => {
|
|
3276
|
+
await getTranscriptCommand(callId, {
|
|
3277
|
+
fields: options.fields
|
|
3278
|
+
});
|
|
3279
|
+
});
|
|
3280
|
+
transcripts.command("analyze <call_id>").description("Analyze a call transcript with performance metrics and insights").option("--fields <fields>", "Comma-separated list of fields to return (e.g., call_id,performance,analysis.summary)").option("--raw", "Return unmodified API response instead of enriched analysis").option("--hotspots-only", "Return only conversation hotspots/issues for troubleshooting").option("--latency-threshold <ms>", `Latency threshold in ms for hotspot detection (default: ${DEFAULT_LATENCY_THRESHOLD})`, String(DEFAULT_LATENCY_THRESHOLD)).option("--silence-threshold <ms>", `Silence threshold in ms for hotspot detection (default: ${DEFAULT_SILENCE_THRESHOLD})`, String(DEFAULT_SILENCE_THRESHOLD)).addHelpText("after", `
|
|
3281
|
+
Examples:
|
|
3282
|
+
$ retell transcripts analyze call_abc123
|
|
3283
|
+
$ retell transcripts analyze call_abc123 --fields call_id,performance
|
|
3284
|
+
$ retell transcripts analyze call_abc123 --raw
|
|
3285
|
+
$ retell transcripts analyze call_abc123 --raw --fields call_id,transcript_object
|
|
3286
|
+
$ retell transcripts analyze call_abc123 --hotspots-only
|
|
3287
|
+
$ retell transcripts analyze call_abc123 --hotspots-only --latency-threshold 1500
|
|
3288
|
+
$ retell transcripts analyze call_abc123 --hotspots-only --fields hotspots
|
|
3289
|
+
$ retell transcripts analyze call_abc123 | jq '.performance.latency_p50_ms'
|
|
3290
|
+
`).action(async (callId, options) => {
|
|
3291
|
+
await analyzeTranscriptCommand(callId, {
|
|
3292
|
+
fields: options.fields,
|
|
3293
|
+
raw: options.raw,
|
|
3294
|
+
hotspotsOnly: options.hotspotsOnly,
|
|
3295
|
+
latencyThreshold: options.latencyThreshold ? parseInt(options.latencyThreshold) : void 0,
|
|
3296
|
+
silenceThreshold: options.silenceThreshold ? parseInt(options.silenceThreshold) : void 0
|
|
3297
|
+
});
|
|
3298
|
+
});
|
|
3299
|
+
transcripts.command("search").description("Search transcripts with advanced filtering").option("--status <status>", "Filter by call status (error, ended, ongoing)").option("--agent-id <id>", "Filter by agent ID").option("--since <date>", "Filter calls after this date (YYYY-MM-DD or ISO format)").option("--until <date>", "Filter calls before this date (YYYY-MM-DD or ISO format)").option("--limit <number>", "Maximum number of results (default: 50)", "50").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
|
|
3300
|
+
Examples:
|
|
3301
|
+
$ retell transcripts search --status error
|
|
3302
|
+
$ retell transcripts search --agent-id agent_123 --since 2025-11-01
|
|
3303
|
+
$ retell transcripts search --status error --limit 10
|
|
3304
|
+
$ retell transcripts search --status error --fields call_id,agent_id,call_status
|
|
3305
|
+
$ retell transcripts search --since 2025-11-01 --until 2025-11-15
|
|
3306
|
+
`).action(async (options) => {
|
|
3307
|
+
await searchTranscriptsCommand({
|
|
3308
|
+
status: options.status,
|
|
3309
|
+
agentId: options.agentId,
|
|
3310
|
+
since: options.since,
|
|
3311
|
+
until: options.until,
|
|
3312
|
+
limit: options.limit ? Number(options.limit) : void 0,
|
|
3313
|
+
fields: options.fields
|
|
3314
|
+
});
|
|
3315
|
+
});
|
|
3316
|
+
var agents = program.command("agents").description("Manage agents");
|
|
3317
|
+
agents.command("list").description("List all agents").option("-l, --limit <number>", "Maximum number of agents to return (default: 100)", "100").option("--fields <fields>", "Comma-separated list of fields to return (e.g., agent_id,agent_name,response_engine_type)").addHelpText("after", `
|
|
3318
|
+
Examples:
|
|
3319
|
+
$ retell agents list
|
|
3320
|
+
$ retell agents list --limit 10
|
|
3321
|
+
$ retell agents list --fields agent_id,agent_name
|
|
3322
|
+
$ retell agents list | jq '.[] | select(.response_engine.type == "retell-llm")'
|
|
3323
|
+
`).action(async (options) => {
|
|
3324
|
+
const limit = parseInt(options.limit, 10);
|
|
3325
|
+
if (isNaN(limit) || limit < 1) {
|
|
3326
|
+
console.error("Error: limit must be a positive number");
|
|
3327
|
+
process.exit(1);
|
|
3328
|
+
}
|
|
3329
|
+
await listAgentsCommand({
|
|
3330
|
+
limit,
|
|
3331
|
+
fields: options.fields
|
|
3332
|
+
});
|
|
3333
|
+
});
|
|
3334
|
+
agents.command("info <agent_id>").description("Get detailed agent information").option("--fields <fields>", "Comma-separated list of fields to return (e.g., agent_name,response_engine.type,voice_config)").addHelpText("after", `
|
|
3335
|
+
Examples:
|
|
3336
|
+
$ retell agents info agent_123abc
|
|
3337
|
+
$ retell agents info agent_123abc --fields agent_name,response_engine.type
|
|
3338
|
+
$ retell agents info agent_123abc | jq '.response_engine.type'
|
|
3339
|
+
`).action(async (agentId, options) => {
|
|
3340
|
+
await agentInfoCommand(agentId, {
|
|
3341
|
+
fields: options.fields
|
|
3342
|
+
});
|
|
3343
|
+
});
|
|
3344
|
+
var prompts = program.command("prompts").description("Manage agent prompts");
|
|
3345
|
+
prompts.command("pull <agent_id>").description("Download agent prompts to a local file").option("-o, --output <path>", "Output file path (default: .retell-prompts/<agent_id>.json)", ".retell-prompts").addHelpText("after", `
|
|
3346
|
+
Examples:
|
|
3347
|
+
$ retell prompts pull agent_123abc
|
|
3348
|
+
$ retell prompts pull agent_123abc --output my-prompts.json
|
|
3349
|
+
`).action(async (agentId, options) => {
|
|
3350
|
+
await pullPromptsCommand(agentId, options);
|
|
3351
|
+
});
|
|
3352
|
+
prompts.command("diff <agent_id>").description("Show differences between local and remote prompts").option("-s, --source <path>", "Source directory path (default: .retell-prompts)", ".retell-prompts").option("-f, --fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
|
|
3353
|
+
Examples:
|
|
3354
|
+
$ retell prompts diff agent_123abc
|
|
3355
|
+
$ retell prompts diff agent_123abc --source ./custom-prompts
|
|
3356
|
+
$ retell prompts diff agent_123abc --fields has_changes,changes.general_prompt
|
|
3357
|
+
`).action(async (agentId, options) => {
|
|
3358
|
+
await diffPromptsCommand(agentId, options);
|
|
3359
|
+
});
|
|
3360
|
+
prompts.command("update <agent_id>").description("Update agent prompts from a local file").option("-s, --source <path>", "Source file path (default: .retell-prompts/<agent_id>.json)", ".retell-prompts").option("--dry-run", "Preview changes without applying them", false).addHelpText("after", `
|
|
3361
|
+
Examples:
|
|
3362
|
+
$ retell prompts update agent_123abc --source my-prompts.json --dry-run
|
|
3363
|
+
$ retell prompts update agent_123abc --source my-prompts.json
|
|
3364
|
+
# Remember to publish: retell agent-publish agent_123abc
|
|
3365
|
+
`).action(async (agentId, options) => {
|
|
3366
|
+
await updatePromptsCommand(agentId, options);
|
|
3367
|
+
});
|
|
3368
|
+
program.command("agent-publish <agent_id>").description("Publish a draft agent to make changes live").addHelpText("after", `
|
|
3369
|
+
Examples:
|
|
3370
|
+
$ retell agent-publish agent_123abc
|
|
3371
|
+
# Run this after updating prompts to make changes live
|
|
3372
|
+
`).action(async (agentId) => {
|
|
3373
|
+
await publishAgentCommand(agentId);
|
|
3374
|
+
});
|
|
3375
|
+
var tools = program.command("tools").description("Manage agent tools (custom functions, webhooks, etc.)");
|
|
3376
|
+
tools.command("list <agent_id>").description("List all tools configured for an agent").option("--state <name>", "Filter by state name (Retell LLM only)").option("--component <id>", "Filter by component ID (Conversation Flow only)").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
|
|
3377
|
+
Examples:
|
|
3378
|
+
$ retell tools list agent_123abc
|
|
3379
|
+
$ retell tools list agent_123abc --state greeting
|
|
3380
|
+
$ retell tools list agent_123abc --fields total_count,general_tools
|
|
3381
|
+
`).action(async (agentId, options) => {
|
|
3382
|
+
await listToolsCommand(agentId, {
|
|
3383
|
+
state: options.state,
|
|
3384
|
+
component: options.component,
|
|
3385
|
+
fields: options.fields
|
|
3386
|
+
});
|
|
3387
|
+
});
|
|
3388
|
+
tools.command("get <agent_id> <tool_name>").description("Get detailed information about a specific tool").option("--state <name>", "State name to search within (Retell LLM only)").option("--component <id>", "Component ID to search within (Conversation Flow only)").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
|
|
3389
|
+
Examples:
|
|
3390
|
+
$ retell tools get agent_123abc lookup_customer
|
|
3391
|
+
$ retell tools get agent_123abc book_cal --state booking
|
|
3392
|
+
$ retell tools get agent_123abc my_tool --fields tool.name,tool.type
|
|
3393
|
+
`).action(async (agentId, toolName, options) => {
|
|
3394
|
+
await getToolCommand(agentId, toolName, {
|
|
3395
|
+
state: options.state,
|
|
3396
|
+
component: options.component,
|
|
3397
|
+
fields: options.fields
|
|
3398
|
+
});
|
|
3399
|
+
});
|
|
3400
|
+
tools.command("add <agent_id>").description("Add a new tool to an agent").requiredOption("-f, --file <path>", "Path to JSON file containing tool definition").option("--state <name>", "Add to specific state (Retell LLM only)").option("--component <id>", "Add to specific component (Conversation Flow only)").option("--dry-run", "Preview changes without applying them").addHelpText("after", `
|
|
3401
|
+
Examples:
|
|
3402
|
+
$ retell tools add agent_123abc --file tool.json
|
|
3403
|
+
$ retell tools add agent_123abc --file tool.json --state booking
|
|
3404
|
+
$ retell tools add agent_123abc --file tool.json --dry-run
|
|
3405
|
+
`).action(async (agentId, options) => {
|
|
3406
|
+
await addToolCommand(agentId, {
|
|
3407
|
+
file: options.file,
|
|
3408
|
+
state: options.state,
|
|
3409
|
+
component: options.component,
|
|
3410
|
+
dryRun: options.dryRun
|
|
3411
|
+
});
|
|
3412
|
+
});
|
|
3413
|
+
tools.command("update <agent_id> <tool_name>").description("Update an existing tool").requiredOption("-f, --file <path>", "Path to JSON file containing updated tool definition").option("--state <name>", "State where tool exists (Retell LLM only)").option("--component <id>", "Component where tool exists (Conversation Flow only)").option("--dry-run", "Preview changes without applying them").addHelpText("after", `
|
|
3414
|
+
Examples:
|
|
3415
|
+
$ retell tools update agent_123abc lookup_customer --file tool.json
|
|
3416
|
+
$ retell tools update agent_123abc book_cal --file tool.json --state booking
|
|
3417
|
+
$ retell tools update agent_123abc my_tool --file tool.json --dry-run
|
|
3418
|
+
`).action(async (agentId, toolName, options) => {
|
|
3419
|
+
await updateToolCommand(agentId, toolName, {
|
|
3420
|
+
file: options.file,
|
|
3421
|
+
state: options.state,
|
|
3422
|
+
component: options.component,
|
|
3423
|
+
dryRun: options.dryRun
|
|
3424
|
+
});
|
|
3425
|
+
});
|
|
3426
|
+
tools.command("remove <agent_id> <tool_name>").description("Remove a tool from an agent").option("--state <name>", "State where tool exists (Retell LLM only)").option("--component <id>", "Component where tool exists (Conversation Flow only)").option("--dry-run", "Preview changes without applying them").addHelpText("after", `
|
|
3427
|
+
Examples:
|
|
3428
|
+
$ retell tools remove agent_123abc lookup_customer
|
|
3429
|
+
$ retell tools remove agent_123abc book_cal --state booking
|
|
3430
|
+
$ retell tools remove agent_123abc my_tool --dry-run
|
|
3431
|
+
`).action(async (agentId, toolName, options) => {
|
|
3432
|
+
await removeToolCommand(agentId, toolName, {
|
|
3433
|
+
state: options.state,
|
|
3434
|
+
component: options.component,
|
|
3435
|
+
dryRun: options.dryRun
|
|
3436
|
+
});
|
|
3437
|
+
});
|
|
3438
|
+
tools.command("export <agent_id>").description("Export all tools from an agent to a JSON file").option("-o, --output <path>", "Output file path (prints to stdout if not specified)").addHelpText("after", `
|
|
3439
|
+
Examples:
|
|
3440
|
+
$ retell tools export agent_123abc
|
|
3441
|
+
$ retell tools export agent_123abc --output tools.json
|
|
3442
|
+
$ retell tools export agent_123abc > tools.json
|
|
3443
|
+
`).action(async (agentId, options) => {
|
|
3444
|
+
await exportToolsCommand(agentId, {
|
|
3445
|
+
output: options.output
|
|
3446
|
+
});
|
|
3447
|
+
});
|
|
3448
|
+
tools.command("import <agent_id>").description("Import tools from a JSON file to an agent").requiredOption("-f, --file <path>", "Path to JSON file containing tools to import").option("--dry-run", "Preview changes without applying them").option("--replace", "Replace existing tools with same name instead of skipping").addHelpText("after", `
|
|
3449
|
+
Examples:
|
|
3450
|
+
$ retell tools import agent_123abc --file tools.json
|
|
3451
|
+
$ retell tools import agent_123abc --file tools.json --dry-run
|
|
3452
|
+
$ retell tools import agent_123abc --file tools.json --replace
|
|
3453
|
+
`).action(async (agentId, options) => {
|
|
3454
|
+
await importToolsCommand(agentId, {
|
|
3455
|
+
file: options.file,
|
|
3456
|
+
dryRun: options.dryRun,
|
|
3457
|
+
replace: options.replace
|
|
3458
|
+
});
|
|
3459
|
+
});
|
|
3460
|
+
var tests = program.command("tests").description("Manage test cases, batch tests, and test runs");
|
|
3461
|
+
var testsCases = tests.command("cases").description("Manage test case definitions");
|
|
3462
|
+
testsCases.command("list").description("List all test case definitions for an LLM or flow").requiredOption("-t, --type <type>", "Response engine type (retell-llm or conversation-flow)").option("--llm-id <id>", "LLM ID (required when type is retell-llm)").option("--flow-id <id>", "Flow ID (required when type is conversation-flow)").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
|
|
3463
|
+
Examples:
|
|
3464
|
+
$ retell tests cases list --type retell-llm --llm-id llm_abc123
|
|
3465
|
+
$ retell tests cases list --type conversation-flow --flow-id cf_abc123
|
|
3466
|
+
$ retell tests cases list --type retell-llm --llm-id llm_abc123 --fields test_case_definitions
|
|
3467
|
+
`).action(async (options) => {
|
|
3468
|
+
if (options.type !== "retell-llm" && options.type !== "conversation-flow") {
|
|
3469
|
+
console.error('Error: type must be "retell-llm" or "conversation-flow"');
|
|
3470
|
+
process.exit(1);
|
|
3471
|
+
}
|
|
3472
|
+
await listTestCasesCommand({
|
|
3473
|
+
type: options.type,
|
|
3474
|
+
llmId: options.llmId,
|
|
3475
|
+
flowId: options.flowId,
|
|
3476
|
+
fields: options.fields
|
|
3477
|
+
});
|
|
3478
|
+
});
|
|
3479
|
+
testsCases.command("get <test_case_definition_id>").description("Get a specific test case definition").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
|
|
3480
|
+
Examples:
|
|
3481
|
+
$ retell tests cases get tcd_abc123
|
|
3482
|
+
$ retell tests cases get tcd_abc123 --fields name,user_prompt
|
|
3483
|
+
`).action(async (testCaseDefinitionId, options) => {
|
|
3484
|
+
await getTestCaseCommand(testCaseDefinitionId, {
|
|
3485
|
+
fields: options.fields
|
|
3486
|
+
});
|
|
3487
|
+
});
|
|
3488
|
+
testsCases.command("create").description("Create a new test case definition from a JSON file").requiredOption("-f, --file <path>", "Path to JSON file containing test case definition").option("--llm-id <id>", "LLM ID (mutually exclusive with --flow-id)").option("--flow-id <id>", "Flow ID (mutually exclusive with --llm-id)").option("--version <number>", "Version of the LLM or flow (optional)").addHelpText("after", `
|
|
3489
|
+
Examples:
|
|
3490
|
+
$ retell tests cases create --file test-case.json --llm-id llm_abc123
|
|
3491
|
+
$ retell tests cases create --file test-case.json --flow-id cf_abc123
|
|
3492
|
+
$ retell tests cases create --file test-case.json --llm-id llm_abc123 --version 2
|
|
3493
|
+
|
|
3494
|
+
Test case JSON format:
|
|
3495
|
+
{
|
|
3496
|
+
"name": "Greeting Test",
|
|
3497
|
+
"user_prompt": "Hello, I need help with my order",
|
|
3498
|
+
"scenario": "User is calling about an order issue",
|
|
3499
|
+
"metrics": ["response_quality", "task_completion"]
|
|
3500
|
+
}
|
|
3501
|
+
`).action(async (options) => {
|
|
3502
|
+
await createTestCaseCommand({
|
|
3503
|
+
file: options.file,
|
|
3504
|
+
llmId: options.llmId,
|
|
3505
|
+
flowId: options.flowId,
|
|
3506
|
+
version: options.version ? parseInt(options.version, 10) : void 0
|
|
3507
|
+
});
|
|
3508
|
+
});
|
|
3509
|
+
testsCases.command("update <test_case_definition_id>").description("Update an existing test case definition from a JSON file").requiredOption("-f, --file <path>", "Path to JSON file containing test case updates").addHelpText("after", `
|
|
3510
|
+
Examples:
|
|
3511
|
+
$ retell tests cases update tcd_abc123 --file test-case.json
|
|
3512
|
+
`).action(async (testCaseDefinitionId, options) => {
|
|
3513
|
+
await updateTestCaseCommand(testCaseDefinitionId, {
|
|
3514
|
+
file: options.file
|
|
3515
|
+
});
|
|
3516
|
+
});
|
|
3517
|
+
testsCases.command("delete <test_case_definition_id>").description("Delete a test case definition").addHelpText("after", `
|
|
3518
|
+
Examples:
|
|
3519
|
+
$ retell tests cases delete tcd_abc123
|
|
3520
|
+
`).action(async (testCaseDefinitionId) => {
|
|
3521
|
+
await deleteTestCaseCommand(testCaseDefinitionId);
|
|
3522
|
+
});
|
|
3523
|
+
var testsBatch = tests.command("batch").description("Manage batch tests");
|
|
3524
|
+
testsBatch.command("list").description("List all batch tests for an LLM or flow").requiredOption("-t, --type <type>", "Response engine type (retell-llm or conversation-flow)").option("--llm-id <id>", "LLM ID (required when type is retell-llm)").option("--flow-id <id>", "Flow ID (required when type is conversation-flow)").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
|
|
3525
|
+
Examples:
|
|
3526
|
+
$ retell tests batch list --type retell-llm --llm-id llm_abc123
|
|
3527
|
+
$ retell tests batch list --type conversation-flow --flow-id cf_abc123
|
|
3528
|
+
$ retell tests batch list --type retell-llm --llm-id llm_abc123 --fields batch_tests
|
|
3529
|
+
`).action(async (options) => {
|
|
3530
|
+
if (options.type !== "retell-llm" && options.type !== "conversation-flow") {
|
|
3531
|
+
console.error('Error: type must be "retell-llm" or "conversation-flow"');
|
|
3532
|
+
process.exit(1);
|
|
3533
|
+
}
|
|
3534
|
+
await listBatchTestsCommand({
|
|
3535
|
+
type: options.type,
|
|
3536
|
+
llmId: options.llmId,
|
|
3537
|
+
flowId: options.flowId,
|
|
3538
|
+
fields: options.fields
|
|
3539
|
+
});
|
|
3540
|
+
});
|
|
3541
|
+
testsBatch.command("get <batch_job_id>").description("Get a specific batch test with its status and stats").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
|
|
3542
|
+
Examples:
|
|
3543
|
+
$ retell tests batch get bjj_abc123
|
|
3544
|
+
$ retell tests batch get bjj_abc123 --fields status,stats
|
|
3545
|
+
`).action(async (batchJobId, options) => {
|
|
3546
|
+
await getBatchTestCommand(batchJobId, {
|
|
3547
|
+
fields: options.fields
|
|
3548
|
+
});
|
|
3549
|
+
});
|
|
3550
|
+
testsBatch.command("create").description("Create a new batch test with specified test case definitions").option("--llm-id <id>", "LLM ID (mutually exclusive with --flow-id)").option("--flow-id <id>", "Flow ID (mutually exclusive with --llm-id)").requiredOption("--cases <ids>", "Comma-separated list of test case definition IDs").option("--version <number>", "Version of the LLM or flow (optional)").addHelpText("after", `
|
|
3551
|
+
Examples:
|
|
3552
|
+
$ retell tests batch create --llm-id llm_abc123 --cases tcd_xxx,tcd_yyy,tcd_zzz
|
|
3553
|
+
$ retell tests batch create --flow-id cf_abc123 --cases tcd_xxx,tcd_yyy
|
|
3554
|
+
$ retell tests batch create --llm-id llm_abc123 --cases tcd_xxx --version 2
|
|
3555
|
+
`).action(async (options) => {
|
|
3556
|
+
await createBatchTestCommand({
|
|
3557
|
+
llmId: options.llmId,
|
|
3558
|
+
flowId: options.flowId,
|
|
3559
|
+
cases: options.cases,
|
|
3560
|
+
version: options.version ? parseInt(options.version, 10) : void 0
|
|
3561
|
+
});
|
|
3562
|
+
});
|
|
3563
|
+
var testsRuns = tests.command("runs").description("View test run results");
|
|
3564
|
+
testsRuns.command("list <batch_job_id>").description("List all test runs for a batch test").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
|
|
3565
|
+
Examples:
|
|
3566
|
+
$ retell tests runs list bjj_abc123
|
|
3567
|
+
$ retell tests runs list bjj_abc123 --fields test_runs
|
|
3568
|
+
`).action(async (batchJobId, options) => {
|
|
3569
|
+
await listTestRunsCommand(batchJobId, {
|
|
3570
|
+
fields: options.fields
|
|
3571
|
+
});
|
|
3572
|
+
});
|
|
3573
|
+
testsRuns.command("get <test_run_id>").description("Get a specific test run result").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
|
|
3574
|
+
Examples:
|
|
3575
|
+
$ retell tests runs get tcj_abc123
|
|
3576
|
+
$ retell tests runs get tcj_abc123 --fields status,metric_results
|
|
3577
|
+
`).action(async (testRunId, options) => {
|
|
3578
|
+
await getTestRunCommand(testRunId, {
|
|
3579
|
+
fields: options.fields
|
|
3580
|
+
});
|
|
3581
|
+
});
|
|
3582
|
+
var kb = program.command("kb").description("Manage RAG knowledge bases");
|
|
3583
|
+
kb.command("list").description("List all knowledge bases").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
|
|
3584
|
+
Examples:
|
|
3585
|
+
$ retell kb list
|
|
3586
|
+
$ retell kb list --fields knowledge_base_id,knowledge_base_name,status
|
|
3587
|
+
`).action(async (options) => {
|
|
3588
|
+
await listKnowledgeBasesCommand({
|
|
3589
|
+
fields: options.fields
|
|
3590
|
+
});
|
|
3591
|
+
});
|
|
3592
|
+
kb.command("get <knowledge_base_id>").description("Get a specific knowledge base").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
|
|
3593
|
+
Examples:
|
|
3594
|
+
$ retell kb get kb_abc123
|
|
3595
|
+
$ retell kb get kb_abc123 --fields knowledge_base_name,status,knowledge_base_sources
|
|
3596
|
+
`).action(async (knowledgeBaseId, options) => {
|
|
3597
|
+
await getKnowledgeBaseCommand(knowledgeBaseId, {
|
|
3598
|
+
fields: options.fields
|
|
3599
|
+
});
|
|
3600
|
+
});
|
|
3601
|
+
kb.command("create").description("Create a new knowledge base").requiredOption("-n, --name <name>", "Knowledge base name (max 40 characters)").option("--urls <urls>", "Comma-separated list of URLs to scrape").option("--texts <file>", "Path to JSON file with text entries [{ title, text }, ...]").option("--auto-refresh", "Enable 12-hour automatic refresh for URL sources").addHelpText("after", `
|
|
3602
|
+
Examples:
|
|
3603
|
+
$ retell kb create --name "Product Docs"
|
|
3604
|
+
$ retell kb create --name "Support KB" --urls https://docs.example.com,https://help.example.com
|
|
3605
|
+
$ retell kb create --name "FAQ" --texts texts.json
|
|
3606
|
+
$ retell kb create --name "Docs" --urls https://docs.example.com --auto-refresh
|
|
3607
|
+
|
|
3608
|
+
Text file format (texts.json):
|
|
3609
|
+
[
|
|
3610
|
+
{ "title": "Getting Started", "text": "Welcome to our product..." },
|
|
3611
|
+
{ "title": "FAQ", "text": "Frequently asked questions..." }
|
|
3612
|
+
]
|
|
3613
|
+
`).action(async (options) => {
|
|
3614
|
+
await createKnowledgeBaseCommand({
|
|
3615
|
+
name: options.name,
|
|
3616
|
+
urls: options.urls,
|
|
3617
|
+
texts: options.texts,
|
|
3618
|
+
autoRefresh: options.autoRefresh
|
|
3619
|
+
});
|
|
3620
|
+
});
|
|
3621
|
+
kb.command("delete <knowledge_base_id>").description("Delete a knowledge base").addHelpText("after", `
|
|
3622
|
+
Examples:
|
|
3623
|
+
$ retell kb delete kb_abc123
|
|
3624
|
+
`).action(async (knowledgeBaseId) => {
|
|
3625
|
+
await deleteKnowledgeBaseCommand(knowledgeBaseId);
|
|
3626
|
+
});
|
|
3627
|
+
var kbSources = kb.command("sources").description("Manage knowledge base sources");
|
|
3628
|
+
kbSources.command("add <knowledge_base_id>").description("Add sources to an existing knowledge base").option("--urls <urls>", "Comma-separated list of URLs to scrape").option("--texts <file>", "Path to JSON file with text entries [{ title, text }, ...]").addHelpText("after", `
|
|
3629
|
+
Examples:
|
|
3630
|
+
$ retell kb sources add kb_abc123 --urls https://docs.example.com/new
|
|
3631
|
+
$ retell kb sources add kb_abc123 --texts additional-texts.json
|
|
3632
|
+
$ retell kb sources add kb_abc123 --urls https://faq.example.com --texts more-texts.json
|
|
3633
|
+
`).action(async (knowledgeBaseId, options) => {
|
|
3634
|
+
await addKnowledgeBaseSourcesCommand(knowledgeBaseId, {
|
|
3635
|
+
urls: options.urls,
|
|
3636
|
+
texts: options.texts
|
|
3637
|
+
});
|
|
3638
|
+
});
|
|
3639
|
+
kbSources.command("delete <knowledge_base_id> <source_id>").description("Remove a source from a knowledge base").addHelpText("after", `
|
|
3640
|
+
Examples:
|
|
3641
|
+
$ retell kb sources delete kb_abc123 source_xyz789
|
|
3642
|
+
`).action(async (knowledgeBaseId, sourceId) => {
|
|
3643
|
+
await deleteKnowledgeBaseSourceCommand(knowledgeBaseId, sourceId);
|
|
3644
|
+
});
|
|
3645
|
+
var flows = program.command("flows").description("Manage conversation flow response engines");
|
|
3646
|
+
flows.command("list").description("List all conversation flows").option("-l, --limit <number>", "Maximum number of flows to return (default: 100, max: 1000)", "100").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
|
|
3647
|
+
Examples:
|
|
3648
|
+
$ retell flows list
|
|
3649
|
+
$ retell flows list --limit 50
|
|
3650
|
+
$ retell flows list --fields conversation_flow_id,version,start_speaker
|
|
3651
|
+
`).action(async (options) => {
|
|
3652
|
+
const limit = parseInt(options.limit, 10);
|
|
3653
|
+
if (isNaN(limit) || limit < 1 || limit > 1e3) {
|
|
3654
|
+
console.error("Error: limit must be a positive number between 1 and 1000");
|
|
3655
|
+
process.exit(1);
|
|
3656
|
+
}
|
|
3657
|
+
await listFlowsCommand({
|
|
3658
|
+
limit,
|
|
3659
|
+
fields: options.fields
|
|
3660
|
+
});
|
|
3661
|
+
});
|
|
3662
|
+
flows.command("get <conversation_flow_id>").description("Get a specific conversation flow").option("--version <number>", "Specific version to retrieve (defaults to latest)").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
|
|
3663
|
+
Examples:
|
|
3664
|
+
$ retell flows get cf_abc123
|
|
3665
|
+
$ retell flows get cf_abc123 --version 2
|
|
3666
|
+
$ retell flows get cf_abc123 --fields conversation_flow_id,nodes,edges
|
|
3667
|
+
`).action(async (conversationFlowId, options) => {
|
|
3668
|
+
await getFlowCommand(conversationFlowId, {
|
|
3669
|
+
version: options.version ? parseInt(options.version, 10) : void 0,
|
|
3670
|
+
fields: options.fields
|
|
3671
|
+
});
|
|
3672
|
+
});
|
|
3673
|
+
flows.command("create").description("Create a new conversation flow from a JSON file").requiredOption("-f, --file <path>", "Path to JSON file containing flow configuration").addHelpText("after", `
|
|
3674
|
+
Examples:
|
|
3675
|
+
$ retell flows create --file flow.json
|
|
3676
|
+
|
|
3677
|
+
Flow JSON format (minimal):
|
|
3678
|
+
{
|
|
3679
|
+
"start_speaker": "agent",
|
|
3680
|
+
"start_node_id": "node_1",
|
|
3681
|
+
"nodes": [...],
|
|
3682
|
+
"edges": [...]
|
|
3683
|
+
}
|
|
3684
|
+
`).action(async (options) => {
|
|
3685
|
+
await createFlowCommand({
|
|
3686
|
+
file: options.file
|
|
3687
|
+
});
|
|
3688
|
+
});
|
|
3689
|
+
flows.command("update <conversation_flow_id>").description("Update an existing conversation flow from a JSON file").requiredOption("-f, --file <path>", "Path to JSON file containing flow updates").option("--version <number>", "Specific version to update (defaults to latest)").addHelpText("after", `
|
|
3690
|
+
Examples:
|
|
3691
|
+
$ retell flows update cf_abc123 --file updates.json
|
|
3692
|
+
$ retell flows update cf_abc123 --file updates.json --version 2
|
|
3693
|
+
`).action(async (conversationFlowId, options) => {
|
|
3694
|
+
await updateFlowCommand(conversationFlowId, {
|
|
3695
|
+
file: options.file,
|
|
3696
|
+
version: options.version ? parseInt(options.version, 10) : void 0
|
|
3697
|
+
});
|
|
3698
|
+
});
|
|
3699
|
+
flows.command("delete <conversation_flow_id>").description("Delete a conversation flow").addHelpText("after", `
|
|
3700
|
+
Examples:
|
|
3701
|
+
$ retell flows delete cf_abc123
|
|
3702
|
+
`).action(async (conversationFlowId) => {
|
|
3703
|
+
await deleteFlowCommand(conversationFlowId);
|
|
1514
3704
|
});
|
|
1515
3705
|
program.parse(process.argv);
|
|
1516
3706
|
if (!process.argv.slice(2).length) {
|