apteva 0.4.53 → 0.4.56

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 (78) 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.jhb45d7r.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.9sryp183.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 +273 -44
  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/api/telemetry.ts +0 -7
  38. package/src/routes/share.ts +3 -2
  39. package/src/server.ts +53 -7
  40. package/src/test-runner.ts +1 -1
  41. package/src/web/App.tsx +37 -22
  42. package/src/web/components/agents/AgentCard.tsx +12 -9
  43. package/src/web/components/agents/AgentPanel.tsx +126 -7
  44. package/src/web/components/agents/AgentsView.tsx +30 -8
  45. package/src/web/components/agents/CreateAgentModal.tsx +155 -5
  46. package/src/web/components/dashboard/Dashboard.tsx +9 -7
  47. package/src/web/components/layout/Sidebar.tsx +43 -32
  48. package/src/web/components/meta-agent/MetaAgent.tsx +6 -2
  49. package/src/web/components/settings/SettingsPage.tsx +172 -43
  50. package/src/web/components/telemetry/TelemetryPage.tsx +54 -46
  51. package/src/web/components/tests/TestsPage.tsx +91 -76
  52. package/src/web/context/TelemetryContext.tsx +4 -1
  53. package/src/web/context/UIModeContext.tsx +49 -0
  54. package/src/web/context/index.ts +3 -0
  55. package/src/web/types.ts +67 -3
  56. package/dist/ActivityPage.sw9p594m.js +0 -3
  57. package/dist/ApiDocsPage.90e03bz7.js +0 -4
  58. package/dist/App.3vnrera5.js +0 -4
  59. package/dist/App.94x6mh7f.js +0 -20
  60. package/dist/App.9t1zc5r7.js +0 -53
  61. package/dist/App.p7jjw1zf.js +0 -4
  62. package/dist/App.pfbdzrhh.js +0 -4
  63. package/dist/App.pse0pzar.js +0 -4
  64. package/dist/App.r43t58w6.js +0 -221
  65. package/dist/App.stgng5bx.js +0 -13
  66. package/dist/App.tm3k7h4b.js +0 -4
  67. package/dist/App.vkg121c6.js +0 -4
  68. package/dist/App.xva0tfzh.js +0 -4
  69. package/dist/App.ysxy7akk.js +0 -61
  70. package/dist/App.yzkh4gq2.js +0 -4
  71. package/dist/ConnectionsPage.q5f9fd37.js +0 -3
  72. package/dist/McpPage.f3ccrezb.js +0 -3
  73. package/dist/SettingsPage.zmzm1pp6.js +0 -3
  74. package/dist/SkillsPage.whxnez67.js +0 -3
  75. package/dist/TasksPage.zp4jfevw.js +0 -3
  76. package/dist/TelemetryPage.an0ky78c.js +0 -3
  77. package/dist/TestsPage.18krj0d1.js +0 -3
  78. package/dist/ThreadsPage.nnphgy98.js +0 -3
@@ -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
  },
@@ -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,67 @@ 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
+ },
881
+ // Analytics
882
+ {
883
+ name: "get_analytics",
884
+ description: "Get analytics summary: total events, LLM calls, tool calls, errors, token usage (input/output/cache/reasoning), and cost. Optionally filter by agent, project, or time range.",
885
+ inputSchema: {
886
+ type: "object",
887
+ properties: {
888
+ agent_id: { type: "string", description: "Filter by agent ID (optional)" },
889
+ project_id: { type: "string", description: "Filter by project ID (optional)" },
890
+ since: { type: "string", description: "ISO timestamp — only include events after this time (optional)" },
891
+ },
892
+ },
893
+ },
894
+ {
895
+ name: "get_usage_by_agent",
896
+ description: "Get token usage and cost breakdown per agent. Returns LLM calls, tool calls, input/output/cache/reasoning tokens, errors, and cost for each agent.",
897
+ inputSchema: {
898
+ type: "object",
899
+ properties: {
900
+ project_id: { type: "string", description: "Filter by project ID (optional)" },
901
+ since: { type: "string", description: "ISO timestamp — only include events after this time (optional)" },
902
+ },
903
+ },
904
+ },
831
905
  ];
832
906
 
833
907
  // Project-only tools — hidden entirely when PROJECTS_ENABLED is not set
@@ -867,15 +941,33 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
867
941
  : AgentDB.findAll();
868
942
  // Exclude meta agent from list
869
943
  const filtered = agents.filter(a => a.id !== META_AGENT_ID);
870
- const result = filtered.map(a => ({
871
- id: a.id,
872
- name: a.name,
873
- provider: a.provider,
874
- model: a.model,
875
- status: a.status,
876
- port: a.port,
877
- projectId: a.project_id,
878
- }));
944
+ const mcpIds = [...new Set(filtered.flatMap(a => a.mcp_servers || []))];
945
+ const skillIds = [...new Set(filtered.flatMap(a => a.skills || []))];
946
+ const mcpMap = McpServerDB.findByIdsLight(mcpIds);
947
+ const skillMap = SkillDB.findByIds(skillIds);
948
+ const result = filtered.map(a => {
949
+ const mcpNames = (a.mcp_servers || [])
950
+ .map(id => mcpMap.get(id)?.name)
951
+ .filter(Boolean);
952
+ const skillNames = (a.skills || [])
953
+ .map(id => skillMap.get(id)?.name)
954
+ .filter(Boolean);
955
+ const enabledFeatures = Object.entries(a.features || {})
956
+ .filter(([, v]) => v === true || (typeof v === "object" && v !== null && (v as any).enabled))
957
+ .map(([k]) => k);
958
+ return {
959
+ id: a.id,
960
+ name: a.name,
961
+ provider: a.provider,
962
+ model: a.model,
963
+ status: a.status,
964
+ projectId: a.project_id,
965
+ features: enabledFeatures,
966
+ mcpServers: mcpNames,
967
+ skills: skillNames,
968
+ systemPrompt: a.system_prompt?.slice(0, 200) + (a.system_prompt && a.system_prompt.length > 200 ? "..." : ""),
969
+ };
970
+ });
879
971
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
880
972
  }
881
973
 
@@ -910,7 +1002,7 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
910
1002
  mcp: args.features?.mcp ?? false,
911
1003
  realtime: false,
912
1004
  files: args.features?.files ?? false,
913
- agents: false,
1005
+ agents: args.features?.agents ? { enabled: true, group: args.project_id || undefined } : false,
914
1006
  },
915
1007
  mcp_servers: [],
916
1008
  skills: [],
@@ -933,7 +1025,14 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
933
1025
  if (args.system_prompt !== undefined) updates.system_prompt = args.system_prompt;
934
1026
  if (args.project_id !== undefined) updates.project_id = args.project_id;
935
1027
  if (args.features !== undefined) {
936
- updates.features = { ...agent.features, ...args.features };
1028
+ const mergedFeatures = { ...agent.features, ...args.features };
1029
+ // Convert agents boolean to MultiAgentConfig
1030
+ if (typeof mergedFeatures.agents === "boolean") {
1031
+ mergedFeatures.agents = mergedFeatures.agents
1032
+ ? { enabled: true, group: args.project_id || agent.project_id || undefined }
1033
+ : false;
1034
+ }
1035
+ updates.features = mergedFeatures;
937
1036
  }
938
1037
 
939
1038
  // Skills: set, add, or remove
@@ -1312,6 +1411,42 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
1312
1411
  }
1313
1412
  }
1314
1413
 
1414
+ case "list_tasks": {
1415
+ const status = args.status || "all";
1416
+ let runningAgents = AgentDB.findAll().filter(a => a.status === "running" && a.port);
1417
+
1418
+ if (args.project_id === "unassigned") {
1419
+ runningAgents = runningAgents.filter(a => !a.project_id);
1420
+ } else if (args.project_id) {
1421
+ runningAgents = runningAgents.filter(a => a.project_id === args.project_id);
1422
+ }
1423
+
1424
+ const allTasks: any[] = [];
1425
+ const results = await Promise.all(
1426
+ runningAgents.map(async (agent) => {
1427
+ try {
1428
+ const data = await fetchFromAgent(agent.id, agent.port!, `/tasks?status=${status}`);
1429
+ return { agent, tasks: data?.tasks || [] };
1430
+ } catch {
1431
+ return { agent, tasks: [] };
1432
+ }
1433
+ })
1434
+ );
1435
+
1436
+ for (const { agent, tasks } of results) {
1437
+ for (const task of tasks) {
1438
+ allTasks.push({
1439
+ id: task.id, title: task.title, type: task.type, status: task.status, priority: task.priority,
1440
+ recurrence: task.recurrence, next_run: task.next_run, execute_at: task.execute_at, created_at: task.created_at,
1441
+ agentId: agent.id, agentName: agent.name,
1442
+ });
1443
+ }
1444
+ }
1445
+
1446
+ allTasks.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
1447
+ return { content: [{ type: "text", text: JSON.stringify({ tasks: allTasks, count: allTasks.length }, null, 2) }] };
1448
+ }
1449
+
1315
1450
  case "list_agent_tasks": {
1316
1451
  const agent = AgentDB.findById(args.agent_id);
1317
1452
  if (!agent) return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
@@ -1491,37 +1626,44 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
1491
1626
  case "list_tests": {
1492
1627
  const tests = TestCaseDB.findAll(args.project_id);
1493
1628
  const result = tests.map(tc => {
1494
- const agent = AgentDB.findById(tc.agent_id);
1629
+ const agent = tc.agent_id ? AgentDB.findById(tc.agent_id) : null;
1495
1630
  const lastRun = TestRunDB.getLatestByTestCase(tc.id);
1496
1631
  return {
1497
1632
  id: tc.id,
1498
1633
  name: tc.name,
1499
- agent_id: tc.agent_id,
1500
- agent_name: agent?.name || "Unknown",
1501
- input_message: tc.input_message,
1502
- eval_criteria: tc.eval_criteria,
1503
- timeout_ms: tc.timeout_ms,
1634
+ behavior: tc.behavior || null,
1635
+ agent_id: tc.agent_id || null,
1636
+ agent_name: agent?.name || (tc.agent_id ? "Unknown" : "Auto-select"),
1504
1637
  last_status: lastRun?.status || null,
1638
+ last_score: lastRun?.score || null,
1505
1639
  last_reasoning: lastRun?.judge_reasoning || null,
1640
+ last_selected_agent: lastRun?.selected_agent_name || null,
1506
1641
  };
1507
1642
  });
1508
1643
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1509
1644
  }
1510
1645
 
1511
1646
  case "create_test": {
1512
- const agent = AgentDB.findById(args.agent_id);
1513
- if (!agent) {
1514
- return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
1647
+ // Validate agent if explicitly provided
1648
+ if (args.agent_id) {
1649
+ const agent = AgentDB.findById(args.agent_id);
1650
+ if (!agent) {
1651
+ return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
1652
+ }
1515
1653
  }
1516
1654
  const tc = TestCaseDB.create({
1517
1655
  name: args.name,
1518
- agent_id: args.agent_id,
1519
- input_message: args.input_message,
1520
- eval_criteria: args.eval_criteria,
1521
- description: args.description,
1522
- timeout_ms: args.timeout_ms,
1656
+ behavior: args.behavior,
1657
+ agent_id: args.agent_id || null,
1658
+ input_message: null,
1659
+ eval_criteria: args.behavior,
1660
+ timeout_ms: args.timeout_ms || 300000,
1661
+ project_id: args.project_id || null,
1523
1662
  });
1524
- return { content: [{ type: "text", text: `Test "${tc.name}" created (id: ${tc.id}) for agent "${agent.name}". Use run_test to execute it.` }] };
1663
+ const agentNote = args.agent_id
1664
+ ? `pinned to agent "${AgentDB.findById(args.agent_id)?.name}"`
1665
+ : "AI will auto-select the best agent";
1666
+ return { content: [{ type: "text", text: `Test "${tc.name}" created (id: ${tc.id}). ${agentNote}. Use run_test to execute it.` }] };
1525
1667
  }
1526
1668
 
1527
1669
  case "run_test": {
@@ -1530,8 +1672,10 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
1530
1672
  return { content: [{ type: "text", text: `Test not found: ${args.test_id}` }], isError: true };
1531
1673
  }
1532
1674
  const result = await runTest(tc);
1533
- const agent = AgentDB.findById(tc.agent_id);
1534
- 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"}` }] };
1675
+ const agentName = result.selected_agent_name || (tc.agent_id ? AgentDB.findById(tc.agent_id)?.name : null) || "unknown";
1676
+ const score = result.score != null ? ` (score: ${result.score}/10)` : "";
1677
+ const duration = result.duration_ms ? ` in ${(result.duration_ms / 1000).toFixed(1)}s` : "";
1678
+ return { content: [{ type: "text", text: `Test "${tc.name}" → ${result.status.toUpperCase()}${score}${duration}\nAgent: ${agentName}\n\nJudge: ${result.judge_reasoning || result.error || "No reasoning"}` }] };
1535
1679
  }
1536
1680
 
1537
1681
  case "run_all_tests": {
@@ -1555,8 +1699,11 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
1555
1699
  const result = runs.map(r => ({
1556
1700
  id: r.id,
1557
1701
  status: r.status,
1702
+ score: r.score,
1558
1703
  duration_ms: r.duration_ms,
1559
1704
  judge_reasoning: r.judge_reasoning,
1705
+ selected_agent: r.selected_agent_name || null,
1706
+ generated_message: r.generated_message || null,
1560
1707
  error: r.error,
1561
1708
  created_at: r.created_at,
1562
1709
  }));
@@ -2035,6 +2182,87 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
2035
2182
  return { content: [{ type: "text", text: `Local add not supported for provider: ${providerId}` }], isError: true };
2036
2183
  }
2037
2184
 
2185
+ case "set_provider_key": {
2186
+ const providerId = args.provider_id;
2187
+ if (!PROVIDERS[providerId as keyof typeof PROVIDERS]) {
2188
+ return { content: [{ type: "text", text: `Unknown provider: ${providerId}. Use list_providers or list_integration_providers to see available providers.` }], isError: true };
2189
+ }
2190
+ if (!args.key) {
2191
+ return { content: [{ type: "text", text: "API key is required" }], isError: true };
2192
+ }
2193
+ const result = await ProviderKeys.save(providerId, args.key, args.project_id || null, null);
2194
+ if (!result.success) {
2195
+ return { content: [{ type: "text", text: `Failed to save key: ${result.error}` }], isError: true };
2196
+ }
2197
+ const provider = PROVIDERS[providerId as keyof typeof PROVIDERS];
2198
+ return { content: [{ type: "text", text: `API key saved for ${provider.name}.` }] };
2199
+ }
2200
+
2201
+ case "start_mcp_server": {
2202
+ const server = McpServerDB.findById(args.server_id);
2203
+ if (!server) {
2204
+ return { content: [{ type: "text", text: `MCP server not found: ${args.server_id}` }], isError: true };
2205
+ }
2206
+ if (server.status === "running") {
2207
+ return { content: [{ type: "text", text: `MCP server "${server.name}" is already running` }] };
2208
+ }
2209
+ try {
2210
+ const port = process.env.PORT || "4280";
2211
+ const res = await fetch(`http://localhost:${port}/api/mcp/servers/${args.server_id}/start`, { method: "POST" });
2212
+ const data = await res.json() as Record<string, unknown>;
2213
+ if (!res.ok) {
2214
+ return { content: [{ type: "text", text: `Failed to start: ${data.error || "unknown error"}` }], isError: true };
2215
+ }
2216
+ return { content: [{ type: "text", text: `MCP server "${server.name}" started. ${data.message || ""}` }] };
2217
+ } catch (err) {
2218
+ return { content: [{ type: "text", text: `Failed to start MCP server: ${err}` }], isError: true };
2219
+ }
2220
+ }
2221
+
2222
+ case "stop_mcp_server": {
2223
+ const server = McpServerDB.findById(args.server_id);
2224
+ if (!server) {
2225
+ return { content: [{ type: "text", text: `MCP server not found: ${args.server_id}` }], isError: true };
2226
+ }
2227
+ if (server.status !== "running") {
2228
+ return { content: [{ type: "text", text: `MCP server "${server.name}" is not running` }] };
2229
+ }
2230
+ try {
2231
+ const port = process.env.PORT || "4280";
2232
+ const res = await fetch(`http://localhost:${port}/api/mcp/servers/${args.server_id}/stop`, { method: "POST" });
2233
+ const data = await res.json() as Record<string, unknown>;
2234
+ if (!res.ok) {
2235
+ return { content: [{ type: "text", text: `Failed to stop: ${data.error || "unknown error"}` }], isError: true };
2236
+ }
2237
+ return { content: [{ type: "text", text: `MCP server "${server.name}" stopped.` }] };
2238
+ } catch (err) {
2239
+ return { content: [{ type: "text", text: `Failed to stop MCP server: ${err}` }], isError: true };
2240
+ }
2241
+ }
2242
+
2243
+ case "get_analytics": {
2244
+ const stats = TelemetryDB.getStats({
2245
+ agentId: args.agent_id || undefined,
2246
+ projectId: args.project_id || undefined,
2247
+ since: args.since || undefined,
2248
+ });
2249
+ return { content: [{ type: "text", text: JSON.stringify(stats, null, 2) }] };
2250
+ }
2251
+
2252
+ case "get_usage_by_agent": {
2253
+ const usage = TelemetryDB.getUsage({
2254
+ project_id: args.project_id || undefined,
2255
+ since: args.since || undefined,
2256
+ group_by: "agent",
2257
+ });
2258
+ // Enrich with agent names
2259
+ const enriched = usage.map(u => {
2260
+ const agent = AgentDB.findById(u.agent_id || "");
2261
+ return { ...u, agent_name: agent?.name || u.agent_id };
2262
+ });
2263
+ return { content: [{ type: "text", text: JSON.stringify(enriched, null, 2) }] };
2264
+ }
2265
+
2038
2266
  default:
2039
2267
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
2040
2268
  }
@@ -2085,23 +2313,24 @@ export async function handlePlatformMcpRequest(req: Request): Promise<Response>
2085
2313
  instructions: `This MCP server controls the Apteva AI agent management platform.
2086
2314
 
2087
2315
  You can manage:
2088
- - 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).
2089
- - 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.
2316
+ - 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).
2317
+ - 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.
2090
2318
  - 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).
2091
- - 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.
2319
+ - 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.
2092
2320
  - 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.
2093
- - PROVIDERS: View which LLM providers have API keys configured.
2094
- - 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.
2321
+ - 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.
2322
+ - 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.
2323
+ - 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.
2095
2324
  - 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.
2096
2325
  - 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.
2097
2326
 
2098
2327
  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.
2099
2328
 
2100
2329
  Typical workflow: list_providers → create_agent → update_agent (add_mcp_servers/add_skills) → start_agent.
2101
- Task workflow: list_agent_tasks → create_agent_task (schedule work) → execute_agent_task (run immediately).
2102
- 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).
2330
+ Task workflow: list_tasks (project-wide overview) or list_agent_tasks (single agent) → create_agent_task (schedule work) → execute_agent_task (run immediately).
2331
+ 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).
2103
2332
  Subscription workflow: list_trigger_providers → list_trigger_types (pick trigger) → list_integration_connections (pick account) → create_subscription (link trigger to agent).
2104
- Test workflow: create_test (set agent, message, eval criteria) → run_test → check results.
2333
+ Test workflow: create_test (describe behavior) → run_test (AI picks agent, generates message, judges result) → get_test_results.
2105
2334
  Always use list_providers first to check which providers have API keys before creating agents.`,
2106
2335
  };
2107
2336
  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
  }