apteva 0.4.54 → 0.4.57

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.
Files changed (76) hide show
  1. package/dist/ActivityPage.kxzzb4yc.js +3 -0
  2. package/dist/ApiDocsPage.zq998hbm.js +4 -0
  3. package/dist/App.55rea8mn.js +61 -0
  4. package/dist/App.5ywb23z4.js +53 -0
  5. package/dist/App.6thds120.js +4 -0
  6. package/dist/{App.ccs4g85x.js → App.9tctxzqm.js} +3 -3
  7. package/dist/App.a8r8ttaz.js +4 -0
  8. package/dist/App.agsv5bje.js +4 -0
  9. package/dist/App.cepapqmx.js +4 -0
  10. package/dist/App.dp041gb3.js +221 -0
  11. package/dist/App.fds72zb5.js +4 -0
  12. package/dist/App.fg9qj2dq.js +4 -0
  13. package/dist/App.ndfejbm9.js +4 -0
  14. package/dist/App.nxmfmq1h.js +13 -0
  15. package/dist/App.qdfyt8ba.js +4 -0
  16. package/dist/{App.g1qhcmpc.js → App.x2d0ygt6.js} +2 -2
  17. package/dist/App.yt9p4nr3.js +20 -0
  18. package/dist/{App.wghtdzsk.js → App.zn4mw16t.js} +1 -1
  19. package/dist/ConnectionsPage.8r96ryw7.js +3 -0
  20. package/dist/McpPage.3cwh0gnd.js +3 -0
  21. package/dist/SettingsPage.ykgdh5ev.js +3 -0
  22. package/dist/SkillsPage.4np1s65b.js +3 -0
  23. package/dist/TasksPage.4g08t7p6.js +3 -0
  24. package/dist/TelemetryPage.72w9pwcp.js +3 -0
  25. package/dist/TestsPage.z4fk3r7r.js +3 -0
  26. package/dist/ThreadsPage.63tcajeh.js +3 -0
  27. package/dist/apteva-kit.css +1 -1
  28. package/dist/index.html +1 -1
  29. package/dist/styles.css +1 -1
  30. package/package.json +2 -2
  31. package/src/crypto.ts +25 -4
  32. package/src/db.ts +24 -1
  33. package/src/mcp-platform.ts +235 -39
  34. package/src/providers.ts +125 -5
  35. package/src/routes/api/agent-utils.ts +105 -8
  36. package/src/routes/api/providers.ts +64 -0
  37. package/src/routes/share.ts +3 -2
  38. package/src/server.ts +53 -7
  39. package/src/test-runner.ts +1 -1
  40. package/src/web/App.tsx +37 -22
  41. package/src/web/components/agents/AgentCard.tsx +12 -9
  42. package/src/web/components/agents/AgentPanel.tsx +126 -7
  43. package/src/web/components/agents/AgentsView.tsx +30 -8
  44. package/src/web/components/agents/CreateAgentModal.tsx +155 -5
  45. package/src/web/components/dashboard/Dashboard.tsx +9 -7
  46. package/src/web/components/layout/Sidebar.tsx +43 -32
  47. package/src/web/components/meta-agent/MetaAgent.tsx +6 -2
  48. package/src/web/components/settings/SettingsPage.tsx +172 -43
  49. package/src/web/components/telemetry/TelemetryPage.tsx +41 -36
  50. package/src/web/components/tests/TestsPage.tsx +91 -76
  51. package/src/web/context/UIModeContext.tsx +49 -0
  52. package/src/web/context/index.ts +3 -0
  53. package/src/web/types.ts +67 -3
  54. package/dist/ActivityPage.k33hj12v.js +0 -3
  55. package/dist/ApiDocsPage.q37747gr.js +0 -4
  56. package/dist/App.3hp80jc2.js +0 -53
  57. package/dist/App.5ebcd85d.js +0 -4
  58. package/dist/App.6fefs2d5.js +0 -4
  59. package/dist/App.794kjn6a.js +0 -4
  60. package/dist/App.c5ebgvec.js +0 -61
  61. package/dist/App.cb1np6f0.js +0 -20
  62. package/dist/App.jemr4v3a.js +0 -221
  63. package/dist/App.kpyf0grm.js +0 -4
  64. package/dist/App.p7zc1bv2.js +0 -13
  65. package/dist/App.qx4wdtjg.js +0 -4
  66. package/dist/App.wjxmwjrp.js +0 -4
  67. package/dist/App.wq1f2jke.js +0 -4
  68. package/dist/App.zx6x0gk2.js +0 -4
  69. package/dist/ConnectionsPage.8b2v07qp.js +0 -3
  70. package/dist/McpPage.3800a82c.js +0 -3
  71. package/dist/SettingsPage.88nj3hbv.js +0 -3
  72. package/dist/SkillsPage.b8pxj5mb.js +0 -3
  73. package/dist/TasksPage.6b3y4b1n.js +0 -3
  74. package/dist/TelemetryPage.7q4d69wj.js +0 -3
  75. package/dist/TestsPage.dpevv5xb.js +0 -3
  76. package/dist/ThreadsPage.1h15363y.js +0 -3
@@ -5,7 +5,7 @@ import { AgentDB, ProjectDB, McpServerDB, McpServerToolDB, SkillDB, TelemetryDB,
5
5
  import { TestCaseDB, TestRunDB } from "./db-tests";
6
6
  import { runTest, runAll } from "./test-runner";
7
7
  import { getProvidersWithStatus, PROVIDERS, ProviderKeys } from "./providers";
8
- import { startAgentProcess, setAgentStatus, toApiAgent, META_AGENT_ID, agentFetch, fetchFromAgent } from "./routes/api/agent-utils";
8
+ import { startAgentProcess, setAgentStatus, toApiAgent, META_AGENT_ID, agentFetch, fetchFromAgent, buildAgentConfig, pushConfigToAgent, pushSkillsToAgent } from "./routes/api/agent-utils";
9
9
  import { agentProcesses } from "./server";
10
10
  import { getTriggerProvider, getTriggerProviderIds, registerTriggerProvider } from "./triggers";
11
11
  import { ComposioTriggerProvider } from "./triggers/composio";
@@ -94,6 +94,7 @@ FEATURES (all optional, default false):
94
94
  - vision: Image & PDF understanding — agent can analyze uploaded images and PDFs.
95
95
  - mcp: MCP tool use — agent can use tools from assigned MCP servers. Enable this if you plan to assign MCP servers.
96
96
  - files: File management — agent can read, write, and manage files in its workspace.
97
+ - agents: Multi-agent communication — agent can call and delegate tasks to peer agents in the same project. Enables call_agent and list_available_agents tools.
97
98
 
98
99
  TIPS:
99
100
  - Always provide a descriptive system_prompt that tells the agent what it does and how to behave.
@@ -116,6 +117,7 @@ TIPS:
116
117
  vision: { type: "boolean", description: "Image and PDF understanding" },
117
118
  mcp: { type: "boolean", description: "MCP tool use — required if assigning MCP servers" },
118
119
  files: { type: "boolean", description: "File read/write in agent workspace" },
120
+ agents: { type: "boolean", description: "Multi-agent communication — call and delegate to peer agents in the same project" },
119
121
  },
120
122
  },
121
123
  },
@@ -124,7 +126,7 @@ TIPS:
124
126
  },
125
127
  {
126
128
  name: "update_agent",
127
- description: `Update an existing agent's configuration. Only provide fields you want to change. If the agent is running, restart it after updating for changes to take effect.
129
+ description: `Update an existing agent's configuration. Only provide fields you want to change. Changes are applied live — if the agent is running, the new config is pushed automatically. No restart needed.
128
130
 
129
131
  SKILLS & MCP SERVERS:
130
132
  - Pass skill_ids or mcp_server_ids to SET the full list (replaces existing).
@@ -150,6 +152,7 @@ SKILLS & MCP SERVERS:
150
152
  vision: { type: "boolean" },
151
153
  mcp: { type: "boolean" },
152
154
  files: { type: "boolean" },
155
+ agents: { type: "boolean" },
153
156
  },
154
157
  },
155
158
  skill_ids: { type: "array", items: { type: "string" }, description: "Set the full list of skill IDs (replaces existing)" },
@@ -382,9 +385,20 @@ EXAMPLES:
382
385
  },
383
386
  },
384
387
  // Task management on agents
388
+ {
389
+ name: "list_tasks",
390
+ description: "List all tasks across ALL running agents in the project. Returns tasks from every agent, each tagged with agentId and agentName. Use this to get a project-wide overview of all tasks.",
391
+ inputSchema: {
392
+ type: "object",
393
+ properties: {
394
+ project_id: { type: "string", description: "Filter to a specific project (optional). If omitted, returns tasks from all running agents." },
395
+ status: { type: "string", description: "Filter by status: all, pending, running, completed, failed, cancelled (default: all)" },
396
+ },
397
+ },
398
+ },
385
399
  {
386
400
  name: "list_agent_tasks",
387
- description: "List tasks on a running agent. Tasks are scheduled work items that agents execute autonomously.",
401
+ description: "List tasks on a specific running agent. Use list_tasks instead for a project-wide view.",
388
402
  inputSchema: {
389
403
  type: "object",
390
404
  properties: {
@@ -532,18 +546,17 @@ EXAMPLES:
532
546
  },
533
547
  {
534
548
  name: "create_test",
535
- description: "Create a new test case for an agent. The test sends a message to the agent, then an LLM judge evaluates the conversation against the success criteria.",
549
+ description: "Create a behavior-driven test case. Describe what the agent should do in natural language the system will auto-select the best agent and generate the test message. Optionally pin to a specific agent.",
536
550
  inputSchema: {
537
551
  type: "object",
538
552
  properties: {
539
553
  name: { type: "string", description: "Test name" },
540
- agent_id: { type: "string", description: "Agent ID to test" },
541
- input_message: { type: "string", description: "Message to send to the agent" },
542
- eval_criteria: { type: "string", description: "Natural language success criteria for the LLM judge. E.g. 'The agent should use the post_tweet tool and confirm the post was made.'" },
543
- description: { type: "string", description: "Optional description" },
544
- timeout_ms: { type: "number", description: "Timeout in ms (default 60000)" },
554
+ behavior: { type: "string", description: "Natural language description of what the agent should do. E.g. 'Search the web for the latest news about AI and summarize it'" },
555
+ agent_id: { type: "string", description: "Optional: pin to a specific agent ID. If omitted, the AI planner auto-selects the best agent." },
556
+ project_id: { type: "string", description: "Optional: project ID to scope agent selection" },
557
+ timeout_ms: { type: "number", description: "Timeout in ms (default 300000 = 5 min)" },
545
558
  },
546
- required: ["name", "agent_id", "input_message", "eval_criteria"],
559
+ required: ["name", "behavior"],
547
560
  },
548
561
  },
549
562
  {
@@ -828,6 +841,43 @@ After adding, use assign_mcp_server_to_agent to give an agent access to these to
828
841
  required: ["provider", "config_id"],
829
842
  },
830
843
  },
844
+ // Provider key management
845
+ {
846
+ name: "set_provider_key",
847
+ description: "Set or update an API key for a provider (LLM or integration). Use list_providers or list_integration_providers to see which providers need keys. Ask the user for the key — never guess or fabricate one.",
848
+ inputSchema: {
849
+ type: "object",
850
+ properties: {
851
+ provider_id: { type: "string", description: "Provider ID (e.g. 'anthropic', 'openai', 'agentdojo', 'composio')" },
852
+ key: { type: "string", description: "The API key value" },
853
+ project_id: { type: "string", description: "Project ID to scope the key to (optional — omit for global key)" },
854
+ },
855
+ required: ["provider_id", "key"],
856
+ },
857
+ },
858
+ // MCP server lifecycle
859
+ {
860
+ name: "start_mcp_server",
861
+ description: "Start an MCP server. npm/pip servers will be installed and launched; local servers are activated immediately; HTTP servers are always available.",
862
+ inputSchema: {
863
+ type: "object",
864
+ properties: {
865
+ server_id: { type: "string", description: "The MCP server ID to start" },
866
+ },
867
+ required: ["server_id"],
868
+ },
869
+ },
870
+ {
871
+ name: "stop_mcp_server",
872
+ description: "Stop a running MCP server.",
873
+ inputSchema: {
874
+ type: "object",
875
+ properties: {
876
+ server_id: { type: "string", description: "The MCP server ID to stop" },
877
+ },
878
+ required: ["server_id"],
879
+ },
880
+ },
831
881
  // Analytics
832
882
  {
833
883
  name: "get_analytics",
@@ -952,14 +1002,17 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
952
1002
  mcp: args.features?.mcp ?? false,
953
1003
  realtime: false,
954
1004
  files: args.features?.files ?? false,
955
- agents: false,
1005
+ agents: args.features?.agents ? { enabled: true, group: args.project_id || undefined } : false,
956
1006
  },
957
1007
  mcp_servers: [],
958
1008
  skills: [],
959
1009
  project_id: args.project_id || null,
960
1010
  });
961
1011
 
962
- return { content: [{ type: "text", text: `Agent created successfully:\n${JSON.stringify({ id: agent.id, name: agent.name, provider: agent.provider, model: agent.model, port: agent.port }, null, 2)}` }] };
1012
+ const enabledFeatures = Object.entries(agent.features)
1013
+ .filter(([_, v]) => v === true || (typeof v === "object" && v?.enabled))
1014
+ .map(([k]) => k === "agents" ? "multi-agent (call_agent, delegate_task, list_available_agents)" : k);
1015
+ return { content: [{ type: "text", text: `Agent created successfully:\n${JSON.stringify({ id: agent.id, name: agent.name, provider: agent.provider, model: agent.model, features: enabledFeatures.length > 0 ? enabledFeatures.join(", ") : "none" }, null, 2)}` }] };
963
1016
  }
964
1017
 
965
1018
  case "update_agent": {
@@ -975,7 +1028,14 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
975
1028
  if (args.system_prompt !== undefined) updates.system_prompt = args.system_prompt;
976
1029
  if (args.project_id !== undefined) updates.project_id = args.project_id;
977
1030
  if (args.features !== undefined) {
978
- updates.features = { ...agent.features, ...args.features };
1031
+ const mergedFeatures = { ...agent.features, ...args.features };
1032
+ // Convert agents boolean to MultiAgentConfig
1033
+ if (typeof mergedFeatures.agents === "boolean") {
1034
+ mergedFeatures.agents = mergedFeatures.agents
1035
+ ? { enabled: true, group: args.project_id || agent.project_id || undefined }
1036
+ : false;
1037
+ }
1038
+ updates.features = mergedFeatures;
979
1039
  }
980
1040
 
981
1041
  // Skills: set, add, or remove
@@ -1017,7 +1077,37 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
1017
1077
  }
1018
1078
 
1019
1079
  const updated = AgentDB.update(args.agent_id, updates);
1020
- return { content: [{ type: "text", text: `Agent updated: ${JSON.stringify({ id: updated?.id, name: updated?.name, skills: updates.skills !== undefined ? updates.skills.length + " skills" : "unchanged", mcp_servers: updates.mcp_servers !== undefined ? updates.mcp_servers.length + " servers" : "unchanged" }, null, 2)}` }] };
1080
+
1081
+ // Push config to running agent (live update, no restart needed)
1082
+ let configPushed = false;
1083
+ if (updated && updated.status === "running" && updated.port) {
1084
+ const providerKey = ProviderKeys.getDecrypted(updated.provider);
1085
+ if (providerKey) {
1086
+ const config = buildAgentConfig(updated, providerKey);
1087
+ const configResult = await pushConfigToAgent(updated.id, updated.port, config);
1088
+ configPushed = configResult.success;
1089
+ // Push skills if any
1090
+ if (config.skills?.definitions?.length > 0) {
1091
+ await pushSkillsToAgent(updated.id, updated.port, config.skills.definitions);
1092
+ }
1093
+ }
1094
+ }
1095
+
1096
+ // Build a clear summary of what changed
1097
+ const summary: Record<string, any> = { id: updated?.id, name: updated?.name };
1098
+ if (updates.features) {
1099
+ const enabledFeatures = Object.entries(updates.features)
1100
+ .filter(([_, v]) => v === true || (typeof v === "object" && v?.enabled))
1101
+ .map(([k]) => k === "agents" ? "multi-agent (call_agent, delegate_task, list_available_agents)" : k);
1102
+ summary.features = enabledFeatures.join(", ") || "none";
1103
+ }
1104
+ if (updates.skills !== undefined) summary.skills = updates.skills.length + " skills";
1105
+ if (updates.mcp_servers !== undefined) summary.mcp_servers = updates.mcp_servers.length + " servers";
1106
+ if (updates.name) summary.name = updates.name;
1107
+ if (updates.model) summary.model = updates.model;
1108
+ if (updates.system_prompt) summary.system_prompt = "updated";
1109
+ if (configPushed) summary.config_status = "applied live (no restart needed)";
1110
+ return { content: [{ type: "text", text: `Agent updated: ${JSON.stringify(summary, null, 2)}` }] };
1021
1111
  }
1022
1112
 
1023
1113
  case "delete_agent": {
@@ -1354,6 +1444,42 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
1354
1444
  }
1355
1445
  }
1356
1446
 
1447
+ case "list_tasks": {
1448
+ const status = args.status || "all";
1449
+ let runningAgents = AgentDB.findAll().filter(a => a.status === "running" && a.port);
1450
+
1451
+ if (args.project_id === "unassigned") {
1452
+ runningAgents = runningAgents.filter(a => !a.project_id);
1453
+ } else if (args.project_id) {
1454
+ runningAgents = runningAgents.filter(a => a.project_id === args.project_id);
1455
+ }
1456
+
1457
+ const allTasks: any[] = [];
1458
+ const results = await Promise.all(
1459
+ runningAgents.map(async (agent) => {
1460
+ try {
1461
+ const data = await fetchFromAgent(agent.id, agent.port!, `/tasks?status=${status}`);
1462
+ return { agent, tasks: data?.tasks || [] };
1463
+ } catch {
1464
+ return { agent, tasks: [] };
1465
+ }
1466
+ })
1467
+ );
1468
+
1469
+ for (const { agent, tasks } of results) {
1470
+ for (const task of tasks) {
1471
+ allTasks.push({
1472
+ id: task.id, title: task.title, type: task.type, status: task.status, priority: task.priority,
1473
+ recurrence: task.recurrence, next_run: task.next_run, execute_at: task.execute_at, created_at: task.created_at,
1474
+ agentId: agent.id, agentName: agent.name,
1475
+ });
1476
+ }
1477
+ }
1478
+
1479
+ allTasks.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
1480
+ return { content: [{ type: "text", text: JSON.stringify({ tasks: allTasks, count: allTasks.length }, null, 2) }] };
1481
+ }
1482
+
1357
1483
  case "list_agent_tasks": {
1358
1484
  const agent = AgentDB.findById(args.agent_id);
1359
1485
  if (!agent) return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
@@ -1533,37 +1659,44 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
1533
1659
  case "list_tests": {
1534
1660
  const tests = TestCaseDB.findAll(args.project_id);
1535
1661
  const result = tests.map(tc => {
1536
- const agent = AgentDB.findById(tc.agent_id);
1662
+ const agent = tc.agent_id ? AgentDB.findById(tc.agent_id) : null;
1537
1663
  const lastRun = TestRunDB.getLatestByTestCase(tc.id);
1538
1664
  return {
1539
1665
  id: tc.id,
1540
1666
  name: tc.name,
1541
- agent_id: tc.agent_id,
1542
- agent_name: agent?.name || "Unknown",
1543
- input_message: tc.input_message,
1544
- eval_criteria: tc.eval_criteria,
1545
- timeout_ms: tc.timeout_ms,
1667
+ behavior: tc.behavior || null,
1668
+ agent_id: tc.agent_id || null,
1669
+ agent_name: agent?.name || (tc.agent_id ? "Unknown" : "Auto-select"),
1546
1670
  last_status: lastRun?.status || null,
1671
+ last_score: lastRun?.score || null,
1547
1672
  last_reasoning: lastRun?.judge_reasoning || null,
1673
+ last_selected_agent: lastRun?.selected_agent_name || null,
1548
1674
  };
1549
1675
  });
1550
1676
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1551
1677
  }
1552
1678
 
1553
1679
  case "create_test": {
1554
- const agent = AgentDB.findById(args.agent_id);
1555
- if (!agent) {
1556
- return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
1680
+ // Validate agent if explicitly provided
1681
+ if (args.agent_id) {
1682
+ const agent = AgentDB.findById(args.agent_id);
1683
+ if (!agent) {
1684
+ return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
1685
+ }
1557
1686
  }
1558
1687
  const tc = TestCaseDB.create({
1559
1688
  name: args.name,
1560
- agent_id: args.agent_id,
1561
- input_message: args.input_message,
1562
- eval_criteria: args.eval_criteria,
1563
- description: args.description,
1564
- timeout_ms: args.timeout_ms,
1689
+ behavior: args.behavior,
1690
+ agent_id: args.agent_id || null,
1691
+ input_message: null,
1692
+ eval_criteria: args.behavior,
1693
+ timeout_ms: args.timeout_ms || 300000,
1694
+ project_id: args.project_id || null,
1565
1695
  });
1566
- return { content: [{ type: "text", text: `Test "${tc.name}" created (id: ${tc.id}) for agent "${agent.name}". Use run_test to execute it.` }] };
1696
+ const agentNote = args.agent_id
1697
+ ? `pinned to agent "${AgentDB.findById(args.agent_id)?.name}"`
1698
+ : "AI will auto-select the best agent";
1699
+ return { content: [{ type: "text", text: `Test "${tc.name}" created (id: ${tc.id}). ${agentNote}. Use run_test to execute it.` }] };
1567
1700
  }
1568
1701
 
1569
1702
  case "run_test": {
@@ -1572,8 +1705,10 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
1572
1705
  return { content: [{ type: "text", text: `Test not found: ${args.test_id}` }], isError: true };
1573
1706
  }
1574
1707
  const result = await runTest(tc);
1575
- const agent = AgentDB.findById(tc.agent_id);
1576
- return { content: [{ type: "text", text: `Test "${tc.name}" (agent: ${agent?.name || tc.agent_id}): ${result.status.toUpperCase()}${result.duration_ms ? ` in ${(result.duration_ms / 1000).toFixed(1)}s` : ""}\n\nJudge: ${result.judge_reasoning || result.error || "No reasoning"}` }] };
1708
+ const agentName = result.selected_agent_name || (tc.agent_id ? AgentDB.findById(tc.agent_id)?.name : null) || "unknown";
1709
+ const score = result.score != null ? ` (score: ${result.score}/10)` : "";
1710
+ const duration = result.duration_ms ? ` in ${(result.duration_ms / 1000).toFixed(1)}s` : "";
1711
+ return { content: [{ type: "text", text: `Test "${tc.name}" → ${result.status.toUpperCase()}${score}${duration}\nAgent: ${agentName}\n\nJudge: ${result.judge_reasoning || result.error || "No reasoning"}` }] };
1577
1712
  }
1578
1713
 
1579
1714
  case "run_all_tests": {
@@ -1597,8 +1732,11 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
1597
1732
  const result = runs.map(r => ({
1598
1733
  id: r.id,
1599
1734
  status: r.status,
1735
+ score: r.score,
1600
1736
  duration_ms: r.duration_ms,
1601
1737
  judge_reasoning: r.judge_reasoning,
1738
+ selected_agent: r.selected_agent_name || null,
1739
+ generated_message: r.generated_message || null,
1602
1740
  error: r.error,
1603
1741
  created_at: r.created_at,
1604
1742
  }));
@@ -2077,6 +2215,64 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
2077
2215
  return { content: [{ type: "text", text: `Local add not supported for provider: ${providerId}` }], isError: true };
2078
2216
  }
2079
2217
 
2218
+ case "set_provider_key": {
2219
+ const providerId = args.provider_id;
2220
+ if (!PROVIDERS[providerId as keyof typeof PROVIDERS]) {
2221
+ return { content: [{ type: "text", text: `Unknown provider: ${providerId}. Use list_providers or list_integration_providers to see available providers.` }], isError: true };
2222
+ }
2223
+ if (!args.key) {
2224
+ return { content: [{ type: "text", text: "API key is required" }], isError: true };
2225
+ }
2226
+ const result = await ProviderKeys.save(providerId, args.key, args.project_id || null, null);
2227
+ if (!result.success) {
2228
+ return { content: [{ type: "text", text: `Failed to save key: ${result.error}` }], isError: true };
2229
+ }
2230
+ const provider = PROVIDERS[providerId as keyof typeof PROVIDERS];
2231
+ return { content: [{ type: "text", text: `API key saved for ${provider.name}.` }] };
2232
+ }
2233
+
2234
+ case "start_mcp_server": {
2235
+ const server = McpServerDB.findById(args.server_id);
2236
+ if (!server) {
2237
+ return { content: [{ type: "text", text: `MCP server not found: ${args.server_id}` }], isError: true };
2238
+ }
2239
+ if (server.status === "running") {
2240
+ return { content: [{ type: "text", text: `MCP server "${server.name}" is already running` }] };
2241
+ }
2242
+ try {
2243
+ const port = process.env.PORT || "4280";
2244
+ const res = await fetch(`http://localhost:${port}/api/mcp/servers/${args.server_id}/start`, { method: "POST" });
2245
+ const data = await res.json() as Record<string, unknown>;
2246
+ if (!res.ok) {
2247
+ return { content: [{ type: "text", text: `Failed to start: ${data.error || "unknown error"}` }], isError: true };
2248
+ }
2249
+ return { content: [{ type: "text", text: `MCP server "${server.name}" started. ${data.message || ""}` }] };
2250
+ } catch (err) {
2251
+ return { content: [{ type: "text", text: `Failed to start MCP server: ${err}` }], isError: true };
2252
+ }
2253
+ }
2254
+
2255
+ case "stop_mcp_server": {
2256
+ const server = McpServerDB.findById(args.server_id);
2257
+ if (!server) {
2258
+ return { content: [{ type: "text", text: `MCP server not found: ${args.server_id}` }], isError: true };
2259
+ }
2260
+ if (server.status !== "running") {
2261
+ return { content: [{ type: "text", text: `MCP server "${server.name}" is not running` }] };
2262
+ }
2263
+ try {
2264
+ const port = process.env.PORT || "4280";
2265
+ const res = await fetch(`http://localhost:${port}/api/mcp/servers/${args.server_id}/stop`, { method: "POST" });
2266
+ const data = await res.json() as Record<string, unknown>;
2267
+ if (!res.ok) {
2268
+ return { content: [{ type: "text", text: `Failed to stop: ${data.error || "unknown error"}` }], isError: true };
2269
+ }
2270
+ return { content: [{ type: "text", text: `MCP server "${server.name}" stopped.` }] };
2271
+ } catch (err) {
2272
+ return { content: [{ type: "text", text: `Failed to stop MCP server: ${err}` }], isError: true };
2273
+ }
2274
+ }
2275
+
2080
2276
  case "get_analytics": {
2081
2277
  const stats = TelemetryDB.getStats({
2082
2278
  agentId: args.agent_id || undefined,
@@ -2150,24 +2346,24 @@ export async function handlePlatformMcpRequest(req: Request): Promise<Response>
2150
2346
  instructions: `This MCP server controls the Apteva AI agent management platform.
2151
2347
 
2152
2348
  You can manage:
2153
- - AGENTS: Create, configure, start, stop, and delete AI agents. Each agent has a provider (LLM), model, system prompt, and optional features (memory, tasks, vision, MCP tools, files). Use update_agent to assign/remove MCP servers (add_mcp_servers, remove_mcp_servers) and skills (add_skills, remove_skills).
2154
- - TASKS: List, create, delete, and execute tasks on running agents. Tasks are scheduled work items that agents execute autonomously. Agents must have the tasks feature enabled. Tools: list_agent_tasks, create_agent_task, delete_agent_task, execute_agent_task.
2349
+ - AGENTS: Create, configure, start, stop, and delete AI agents. Each agent has a provider (LLM), model, system prompt, and optional features (memory, tasks, vision, MCP tools, files, agents/multi-agent). The "agents" feature enables multi-agent communication — agents can call and delegate tasks to peer agents in the same project. Use update_agent to assign/remove MCP servers (add_mcp_servers, remove_mcp_servers) and skills (add_skills, remove_skills).
2350
+ - TASKS: List, create, delete, and execute tasks. Use list_tasks for a project-wide overview of all tasks across all running agents. Use list_agent_tasks for tasks on a specific agent. Agents must have the tasks feature enabled. Tools: list_tasks, list_agent_tasks, create_agent_task, delete_agent_task, execute_agent_task.
2155
2351
  - PROJECTS: Organize agents into projects. Tools: list_projects, create_project, update_project, delete_project. Use project tools when the user is viewing All Projects. Deleting a project unassigns its agents (does not delete them).
2156
- - MCP SERVERS: Tool integrations that give agents capabilities (web search, file access, APIs). Use update_agent with add_mcp_servers to assign servers to agents.
2352
+ - MCP SERVERS: Tool integrations that give agents capabilities (web search, file access, APIs). Use update_agent with add_mcp_servers to assign servers to agents. Use start_mcp_server/stop_mcp_server to activate/deactivate servers.
2157
2353
  - SKILLS: Reusable instruction sets that specialize agent behavior. Use create_skill to create new skills (pass project_id from context to scope to the current project). Use update_agent with add_skills/remove_skills to assign/unassign skills to agents. Tools: list_skills, get_skill, create_skill, update_skill, toggle_skill, delete_skill.
2158
2354
  - ANALYTICS: View token usage, costs, and activity stats across agents. Tools: get_analytics (summary totals), get_usage_by_agent (per-agent breakdown). Both support filtering by agent, project, or time range.
2159
- - PROVIDERS: View which LLM providers have API keys configured.
2160
- - TESTS: Create and run automated tests for agent workflows. Tests send a message to an agent, then an LLM judge evaluates the response against success criteria. Tools: list_tests, create_test, run_test, run_all_tests, get_test_results, delete_test.
2355
+ - PROVIDERS: View and configure API keys for LLM and integration providers. Tools: list_providers, set_provider_key. If a provider key is missing, ask the user for it and save with set_provider_key.
2356
+ - TESTS: Create behavior-driven tests for agent workflows. Describe what the agent should do in natural language — the system auto-selects the best agent and generates a test message. An LLM judge evaluates the result and scores 1-10. Tools: list_tests, create_test, run_test, run_all_tests, get_test_results, delete_test.
2161
2357
  - SUBSCRIPTIONS & TRIGGERS: Subscribe agents to external events (webhooks). Supports multiple providers (composio, agentdojo). Use list_trigger_providers → list_trigger_types → list_integration_connections → create_subscription. Manage with enable_subscription, disable_subscription, delete_subscription, list_subscriptions.
2162
2358
  - INTEGRATIONS: Connect third-party apps and create MCP servers from them. Supports agentdojo and composio providers. Use list_integration_providers → list_integration_apps → connect_integration_app (API key) → create_integration_config → add_integration_config_locally → then update_agent with add_mcp_servers to assign to an agent. For OAuth apps, direct the user to the Browse Toolkits UI.
2163
2359
 
2164
2360
  CRITICAL: ALWAYS pass project_id to every tool call that accepts it. API keys and resources are scoped per project — calls without project_id will fail. The chat context tells you the current project id.
2165
2361
 
2166
2362
  Typical workflow: list_providers → create_agent → update_agent (add_mcp_servers/add_skills) → start_agent.
2167
- Task workflow: list_agent_tasks → create_agent_task (schedule work) → execute_agent_task (run immediately).
2168
- Integration workflow: list_integration_providers → list_integration_apps (browse) → connect_integration_app (API key) → create_integration_config → add_integration_config_locally → update_agent (add_mcp_servers).
2363
+ Task workflow: list_tasks (project-wide overview) or list_agent_tasks (single agent) → create_agent_task (schedule work) → execute_agent_task (run immediately).
2364
+ Integration workflow: list_integration_providers → if no key, ask user and set_provider_key → list_integration_apps (browse) → connect_integration_app (API key) → create_integration_config → add_integration_config_locally → start_mcp_server → update_agent (add_mcp_servers).
2169
2365
  Subscription workflow: list_trigger_providers → list_trigger_types (pick trigger) → list_integration_connections (pick account) → create_subscription (link trigger to agent).
2170
- Test workflow: create_test (set agent, message, eval criteria) → run_test → check results.
2366
+ Test workflow: create_test (describe behavior) → run_test (AI picks agent, generates message, judges result) → get_test_results.
2171
2367
  Always use list_providers first to check which providers have API keys before creating agents.`,
2172
2368
  };
2173
2369
  break;
package/src/providers.ts CHANGED
@@ -13,7 +13,6 @@ export const PROVIDERS = {
13
13
  testEndpoint: "https://api.anthropic.com/v1/messages",
14
14
  models: [
15
15
  { value: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", recommended: true, input_cost: 3, output_cost: 15, cache_creation_cost: 3.75, cache_read_cost: 0.3 },
16
- { value: "claude-sonnet-4-5", label: "Claude Sonnet 4.5", input_cost: 3, output_cost: 15, cache_creation_cost: 3.75, cache_read_cost: 0.3 },
17
16
  { value: "claude-haiku-4-5", label: "Claude Haiku 4.5 (Fast)", input_cost: 0.8, output_cost: 4, cache_creation_cost: 1, cache_read_cost: 0.08 },
18
17
  ],
19
18
  },
@@ -26,8 +25,7 @@ export const PROVIDERS = {
26
25
  docsUrl: "https://platform.openai.com/api-keys",
27
26
  testEndpoint: "https://api.openai.com/v1/models",
28
27
  models: [
29
- { value: "gpt-4o", label: "GPT-4o", recommended: true, input_cost: 2.5, output_cost: 10 },
30
- { value: "gpt-4o-mini", label: "GPT-4o Mini (Fast)", input_cost: 0.15, output_cost: 0.6 },
28
+ { value: "gpt-5.4", label: "GPT-5.4 (Latest)", recommended: true, input_cost: 2.5, output_cost: 10 },
31
29
  ],
32
30
  },
33
31
  groq: {
@@ -52,8 +50,10 @@ export const PROVIDERS = {
52
50
  docsUrl: "https://aistudio.google.com/app/apikey",
53
51
  testEndpoint: "https://generativelanguage.googleapis.com/v1/models",
54
52
  models: [
55
- { value: "gemini-3-pro-preview", label: "Gemini 3 Pro Preview (Latest)", recommended: true, input_cost: 2, output_cost: 12 },
56
- { value: "gemini-3-flash-preview", label: "Gemini 3 Flash Preview (Fast)", input_cost: 0.5, output_cost: 3 },
53
+ { value: "gemini-3.1-pro-preview", label: "Gemini 3.1 Pro Preview (Latest)", recommended: true, input_cost: 2, output_cost: 12 },
54
+ { value: "gemini-3-pro-preview", label: "Gemini 3 Pro Preview", input_cost: 2, output_cost: 12 },
55
+ { value: "gemini-3.1-flash-lite-preview", label: "Gemini 3.1 Flash Lite Preview (Fast)", input_cost: 0.25, output_cost: 1.5 },
56
+ { value: "gemini-3-flash-preview", label: "Gemini 3 Flash Preview", input_cost: 0.5, output_cost: 3 },
57
57
  ],
58
58
  },
59
59
  xai: {
@@ -126,6 +126,20 @@ export const PROVIDERS = {
126
126
  { value: "venice-uncensored", label: "Venice Uncensored 1.1", input_cost: 0.2, output_cost: 0.9 },
127
127
  ],
128
128
  },
129
+ cerebras: {
130
+ id: "cerebras",
131
+ name: "Cerebras",
132
+ displayName: "Cerebras",
133
+ type: "llm" as const,
134
+ envVar: "CEREBRAS_API_KEY",
135
+ docsUrl: "https://cloud.cerebras.ai/",
136
+ testEndpoint: "https://api.cerebras.ai/v1/models",
137
+ models: [
138
+ { value: "gpt-oss-120b", label: "GPT-OSS 120B", recommended: true, input_cost: 0.2, output_cost: 1 },
139
+ { value: "llama-4-scout-17b-16e-instruct", label: "Llama 4 Scout 17B (Fast)", input_cost: 0.05, output_cost: 0.25 },
140
+ { value: "llama3.3-70b", label: "Llama 3.3 70B", input_cost: 0.1, output_cost: 0.5 },
141
+ ],
142
+ },
129
143
  ollama: {
130
144
  id: "ollama",
131
145
  name: "Ollama",
@@ -187,6 +201,110 @@ export const PROVIDERS = {
187
201
  isLocal: true,
188
202
  models: [],
189
203
  },
204
+ // Voice Providers
205
+ elevenlabs: {
206
+ id: "elevenlabs",
207
+ name: "ElevenLabs",
208
+ displayName: "ElevenLabs",
209
+ type: "voice" as const,
210
+ envVar: "ELEVENLABS_API_KEY",
211
+ docsUrl: "https://elevenlabs.io/app/settings/api-keys",
212
+ description: "Speech-to-text and text-to-speech for voice conversations",
213
+ models: [],
214
+ },
215
+ deepgram: {
216
+ id: "deepgram",
217
+ name: "Deepgram",
218
+ displayName: "Deepgram",
219
+ type: "voice" as const,
220
+ envVar: "DEEPGRAM_API_KEY",
221
+ docsUrl: "https://console.deepgram.com/",
222
+ description: "Fast speech-to-text and text-to-speech with low latency",
223
+ models: [],
224
+ },
225
+ // Local Voice Providers
226
+ speaches: {
227
+ id: "speaches",
228
+ name: "Speaches",
229
+ displayName: "Speaches (Local)",
230
+ type: "voice" as const,
231
+ voiceSubtype: "both" as const,
232
+ envVar: "SPEACHES_BASE_URL",
233
+ docsUrl: "https://github.com/speaches-ai/speaches",
234
+ description: "Local STT + TTS server with OpenAI-compatible API",
235
+ isLocal: true,
236
+ defaultBaseUrl: "http://localhost:8000",
237
+ models: [
238
+ { value: "Systran/faster-whisper-large-v3", label: "Whisper Large V3 (STT)", recommended: true, input_cost: 0, output_cost: 0 },
239
+ { value: "Systran/faster-whisper-medium", label: "Whisper Medium (STT)", input_cost: 0, output_cost: 0 },
240
+ { value: "hexgrad/Kokoro-82M", label: "Kokoro 82M (TTS)", recommended: true, input_cost: 0, output_cost: 0 },
241
+ ],
242
+ },
243
+ whisper_cpp: {
244
+ id: "whisper_cpp",
245
+ name: "Whisper.cpp",
246
+ displayName: "Whisper.cpp (Local)",
247
+ type: "voice" as const,
248
+ voiceSubtype: "stt" as const,
249
+ envVar: "WHISPER_CPP_BASE_URL",
250
+ docsUrl: "https://github.com/ggerganov/whisper.cpp",
251
+ description: "High-performance local speech-to-text",
252
+ isLocal: true,
253
+ defaultBaseUrl: "http://localhost:8080",
254
+ models: [
255
+ { value: "large-v3", label: "Large V3", recommended: true, input_cost: 0, output_cost: 0 },
256
+ { value: "large-v3-turbo", label: "Large V3 Turbo (Fast)", input_cost: 0, output_cost: 0 },
257
+ { value: "medium", label: "Medium", input_cost: 0, output_cost: 0 },
258
+ { value: "small", label: "Small (Fast)", input_cost: 0, output_cost: 0 },
259
+ { value: "base", label: "Base (Fastest)", input_cost: 0, output_cost: 0 },
260
+ ],
261
+ },
262
+ kokoro: {
263
+ id: "kokoro",
264
+ name: "Kokoro",
265
+ displayName: "Kokoro (Local)",
266
+ type: "voice" as const,
267
+ voiceSubtype: "tts" as const,
268
+ envVar: "KOKORO_BASE_URL",
269
+ docsUrl: "https://github.com/remsky/Kokoro-FastAPI",
270
+ description: "High-quality local text-to-speech, CPU-only, 82M params",
271
+ isLocal: true,
272
+ defaultBaseUrl: "http://localhost:8880",
273
+ models: [
274
+ { value: "kokoro", label: "Kokoro 82M", recommended: true, input_cost: 0, output_cost: 0 },
275
+ ],
276
+ },
277
+ piper: {
278
+ id: "piper",
279
+ name: "Piper",
280
+ displayName: "Piper (Local)",
281
+ type: "voice" as const,
282
+ voiceSubtype: "tts" as const,
283
+ envVar: "PIPER_BASE_URL",
284
+ docsUrl: "https://github.com/rhasspy/piper",
285
+ description: "Fast local text-to-speech, runs on any hardware",
286
+ isLocal: true,
287
+ defaultBaseUrl: "http://localhost:5000",
288
+ models: [
289
+ { value: "en_US-amy-medium", label: "Amy (Medium)", recommended: true, input_cost: 0, output_cost: 0 },
290
+ { value: "en_US-lessac-medium", label: "Lessac (Medium)", input_cost: 0, output_cost: 0 },
291
+ ],
292
+ },
293
+ fish_speech: {
294
+ id: "fish_speech",
295
+ name: "Fish Speech",
296
+ displayName: "Fish Speech (Local)",
297
+ type: "voice" as const,
298
+ voiceSubtype: "tts" as const,
299
+ envVar: "FISH_SPEECH_BASE_URL",
300
+ docsUrl: "https://github.com/fishaudio/fish-speech",
301
+ description: "High-quality local TTS with voice cloning",
302
+ isLocal: true,
303
+ defaultBaseUrl: "http://localhost:8180",
304
+ models: [
305
+ { value: "fish-speech-1.5", label: "Fish Speech 1.5", recommended: true, input_cost: 0, output_cost: 0 },
306
+ ],
307
+ },
190
308
  // MCP Integrations
191
309
  composio: {
192
310
  id: "composio",
@@ -473,5 +591,7 @@ export function getProvidersWithStatus() {
473
591
  keyHint: keyStatuses.get(provider.id)?.key_hint || null,
474
592
  isValid: keyStatuses.get(provider.id)?.is_valid ?? null,
475
593
  isLocal: "isLocal" in provider ? provider.isLocal : undefined,
594
+ voiceSubtype: "voiceSubtype" in provider ? provider.voiceSubtype : undefined,
595
+ defaultBaseUrl: "defaultBaseUrl" in provider ? provider.defaultBaseUrl : undefined,
476
596
  }));
477
597
  }