apteva 0.4.32 → 0.4.44
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/dist/ActivityPage.c48n83h2.js +3 -0
- package/dist/ApiDocsPage.yzcxx5ax.js +4 -0
- package/dist/App.09yb8t0b.js +1 -0
- package/dist/App.152mbs1r.js +4 -0
- package/dist/App.3a67nx9w.js +4 -0
- package/dist/App.9epx6785.js +4 -0
- package/dist/App.d8955awp.js +4 -0
- package/dist/App.drwb57jq.js +4 -0
- package/dist/App.gssbmajb.js +4 -0
- package/dist/App.qw70pc29.js +53 -0
- package/dist/App.qzbx5wtj.js +4 -0
- package/dist/App.r5serxkt.js +8 -0
- package/dist/App.tpmp9020.js +20 -0
- package/dist/App.v2wb4d7d.js +61 -0
- package/dist/App.vxmaaj0m.js +13 -0
- package/dist/App.w4p2tda9.js +4 -0
- package/dist/App.wv2ng55q.js +221 -0
- package/dist/App.yncnrn0f.js +4 -0
- package/dist/ConnectionsPage.k6cspyqq.js +3 -0
- package/dist/McpPage.cdxm48xj.js +3 -0
- package/dist/SettingsPage.evpv7c2y.js +3 -0
- package/dist/SkillsPage.pvzp6c1a.js +3 -0
- package/dist/TasksPage.6jnvbpsy.js +3 -0
- package/dist/TelemetryPage.t7vk24zc.js +3 -0
- package/dist/TestsPage.5x6658aa.js +3 -0
- package/dist/ThreadsPage.3fvhtevh.js +3 -0
- package/dist/apteva-kit.css +1 -1
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +10 -9
- package/src/crypto.ts +4 -3
- package/src/db.ts +171 -36
- package/src/integrations/agentdojo.ts +95 -12
- package/src/integrations/index.ts +7 -0
- package/src/mcp-platform.ts +870 -142
- package/src/openapi.ts +96 -0
- package/src/providers.ts +60 -34
- package/src/routes/api/agent-utils.ts +59 -47
- package/src/routes/api/agents.ts +71 -2
- package/src/routes/api/integrations.ts +11 -5
- package/src/routes/api/mcp.ts +5 -4
- package/src/routes/api/meta-agent.ts +37 -1
- package/src/routes/api/projects.ts +3 -3
- package/src/routes/api/providers.ts +121 -30
- package/src/routes/api/skills.ts +2 -3
- package/src/routes/api/system.ts +98 -14
- package/src/routes/api/telemetry.ts +19 -1
- package/src/routes/share.ts +85 -0
- package/src/server.ts +43 -32
- package/src/triggers/agentdojo.ts +2 -2
- package/src/web/App.tsx +107 -21
- package/src/web/components/activity/ActivityPage.tsx +242 -389
- package/src/web/components/agents/AgentCard.tsx +19 -27
- package/src/web/components/agents/AgentPanel.tsx +358 -198
- package/src/web/components/agents/AgentsView.tsx +4 -4
- package/src/web/components/agents/CreateAgentModal.tsx +21 -79
- package/src/web/components/api/ApiDocsPage.tsx +66 -66
- package/src/web/components/auth/CreateAccountStep.tsx +16 -16
- package/src/web/components/auth/LoginPage.tsx +10 -10
- package/src/web/components/common/Icons.tsx +8 -0
- package/src/web/components/common/LoadingSpinner.tsx +2 -2
- package/src/web/components/common/Modal.tsx +8 -8
- package/src/web/components/common/Select.tsx +11 -10
- package/src/web/components/connections/ConnectionsPage.tsx +4 -4
- package/src/web/components/connections/IntegrationsTab.tsx +18 -18
- package/src/web/components/connections/OverviewTab.tsx +13 -13
- package/src/web/components/connections/TriggersTab.tsx +99 -99
- package/src/web/components/dashboard/Dashboard.tsx +177 -52
- package/src/web/components/index.ts +1 -1
- package/src/web/components/layout/Header.tsx +50 -34
- package/src/web/components/layout/Sidebar.tsx +41 -16
- package/src/web/components/mcp/IntegrationsPanel.tsx +160 -69
- package/src/web/components/mcp/McpPage.tsx +218 -209
- package/src/web/components/meta-agent/MetaAgent.tsx +15 -11
- package/src/web/components/onboarding/OnboardingWizard.tsx +25 -25
- package/src/web/components/settings/SettingsPage.tsx +389 -221
- package/src/web/components/skills/SkillsPage.tsx +88 -88
- package/src/web/components/tasks/TasksPage.tsx +385 -68
- package/src/web/components/telemetry/TelemetryPage.tsx +294 -39
- package/src/web/components/tests/TestsPage.tsx +50 -50
- package/src/web/components/threads/ThreadsPage.tsx +315 -0
- package/src/web/context/AuthContext.tsx +3 -3
- package/src/web/context/ProjectContext.tsx +8 -3
- package/src/web/context/TelemetryContext.tsx +24 -6
- package/src/web/context/ThemeContext.tsx +69 -0
- package/src/web/context/index.ts +3 -1
- package/src/web/styles.css +25 -7
- package/src/web/themes.ts +99 -0
- package/src/web/types.ts +4 -7
- package/dist/ActivityPage.41nbye4r.js +0 -3
- package/dist/ApiDocsPage.4smnt8m3.js +0 -4
- package/dist/App.0sbax9et.js +0 -4
- package/dist/App.0ws427h8.js +0 -4
- package/dist/App.6q6bar8b.js +0 -4
- package/dist/App.80301vdb.js +0 -4
- package/dist/App.af2wg84v.js +0 -267
- package/dist/App.ca1rz1ph.js +0 -4
- package/dist/App.ensa6z0r.js +0 -4
- package/dist/App.f8g7tych.js +0 -13
- package/dist/App.mvtqv6qc.js +0 -20
- package/dist/App.ncgc9cxy.js +0 -4
- package/dist/App.p02f4ret.js +0 -1
- package/dist/App.p0fb1pds.js +0 -4
- package/dist/App.pmaq48sj.js +0 -4
- package/dist/App.yv87t9m5.js +0 -4
- package/dist/App.zjmfm8p6.js +0 -4
- package/dist/ConnectionsPage.anb3rv9a.js +0 -3
- package/dist/McpPage.y396h6fy.js +0 -3
- package/dist/SettingsPage.p1hc60gk.js +0 -3
- package/dist/SkillsPage.yj3xdsay.js +0 -3
- package/dist/TasksPage.sjv0khtv.js +0 -3
- package/dist/TelemetryPage.2qm4w16r.js +0 -3
- package/dist/TestsPage.zzs4qfj8.js +0 -3
package/src/openapi.ts
CHANGED
|
@@ -1006,6 +1006,40 @@ while (true) {
|
|
|
1006
1006
|
},
|
|
1007
1007
|
},
|
|
1008
1008
|
},
|
|
1009
|
+
"/tasks/{agentId}": {
|
|
1010
|
+
post: {
|
|
1011
|
+
tags: ["Tasks"],
|
|
1012
|
+
summary: "Create a task on an agent",
|
|
1013
|
+
description: "Create a new task on a running agent. The agent must have the tasks feature enabled.",
|
|
1014
|
+
parameters: [
|
|
1015
|
+
{ name: "agentId", in: "path", required: true, schema: { type: "string" }, description: "Agent ID to create the task on" },
|
|
1016
|
+
],
|
|
1017
|
+
requestBody: {
|
|
1018
|
+
required: true,
|
|
1019
|
+
content: {
|
|
1020
|
+
"application/json": {
|
|
1021
|
+
schema: {
|
|
1022
|
+
type: "object",
|
|
1023
|
+
properties: {
|
|
1024
|
+
title: { type: "string", description: "Task title" },
|
|
1025
|
+
description: { type: "string", description: "Task description" },
|
|
1026
|
+
type: { type: "string", enum: ["once", "recurring"], default: "once" },
|
|
1027
|
+
priority: { type: "integer", minimum: 1, maximum: 10, default: 5 },
|
|
1028
|
+
execute_at: { type: "string", format: "date-time", description: "Scheduled execution time (for one-time tasks)" },
|
|
1029
|
+
recurrence: { type: "string", description: "Cron expression (for recurring tasks)" },
|
|
1030
|
+
},
|
|
1031
|
+
required: ["title"],
|
|
1032
|
+
},
|
|
1033
|
+
},
|
|
1034
|
+
},
|
|
1035
|
+
},
|
|
1036
|
+
responses: {
|
|
1037
|
+
"201": { description: "Task created successfully" },
|
|
1038
|
+
"400": { description: "Agent is not running or invalid input" },
|
|
1039
|
+
"404": { description: "Agent not found" },
|
|
1040
|
+
},
|
|
1041
|
+
},
|
|
1042
|
+
},
|
|
1009
1043
|
"/tasks/{agentId}/{taskId}": {
|
|
1010
1044
|
get: {
|
|
1011
1045
|
tags: ["Tasks"],
|
|
@@ -1033,6 +1067,68 @@ while (true) {
|
|
|
1033
1067
|
"404": { description: "Agent not found" },
|
|
1034
1068
|
},
|
|
1035
1069
|
},
|
|
1070
|
+
put: {
|
|
1071
|
+
tags: ["Tasks"],
|
|
1072
|
+
summary: "Update a task on an agent",
|
|
1073
|
+
description: "Update an existing task on a running agent.",
|
|
1074
|
+
parameters: [
|
|
1075
|
+
{ name: "agentId", in: "path", required: true, schema: { type: "string" }, description: "Agent ID" },
|
|
1076
|
+
{ name: "taskId", in: "path", required: true, schema: { type: "string" }, description: "Task ID to update" },
|
|
1077
|
+
],
|
|
1078
|
+
requestBody: {
|
|
1079
|
+
required: true,
|
|
1080
|
+
content: {
|
|
1081
|
+
"application/json": {
|
|
1082
|
+
schema: {
|
|
1083
|
+
type: "object",
|
|
1084
|
+
properties: {
|
|
1085
|
+
title: { type: "string" },
|
|
1086
|
+
description: { type: "string" },
|
|
1087
|
+
type: { type: "string", enum: ["once", "recurring"] },
|
|
1088
|
+
priority: { type: "integer", minimum: 1, maximum: 10 },
|
|
1089
|
+
execute_at: { type: "string", format: "date-time" },
|
|
1090
|
+
recurrence: { type: "string" },
|
|
1091
|
+
},
|
|
1092
|
+
},
|
|
1093
|
+
},
|
|
1094
|
+
},
|
|
1095
|
+
},
|
|
1096
|
+
responses: {
|
|
1097
|
+
"200": { description: "Task updated" },
|
|
1098
|
+
"400": { description: "Agent is not running or invalid input" },
|
|
1099
|
+
"404": { description: "Agent or task not found" },
|
|
1100
|
+
},
|
|
1101
|
+
},
|
|
1102
|
+
delete: {
|
|
1103
|
+
tags: ["Tasks"],
|
|
1104
|
+
summary: "Delete a task on an agent",
|
|
1105
|
+
description: "Delete a task from a running agent.",
|
|
1106
|
+
parameters: [
|
|
1107
|
+
{ name: "agentId", in: "path", required: true, schema: { type: "string" }, description: "Agent ID" },
|
|
1108
|
+
{ name: "taskId", in: "path", required: true, schema: { type: "string" }, description: "Task ID to delete" },
|
|
1109
|
+
],
|
|
1110
|
+
responses: {
|
|
1111
|
+
"200": { description: "Task deleted" },
|
|
1112
|
+
"400": { description: "Agent is not running" },
|
|
1113
|
+
"404": { description: "Agent or task not found" },
|
|
1114
|
+
},
|
|
1115
|
+
},
|
|
1116
|
+
},
|
|
1117
|
+
"/tasks/{agentId}/{taskId}/execute": {
|
|
1118
|
+
post: {
|
|
1119
|
+
tags: ["Tasks"],
|
|
1120
|
+
summary: "Execute a task immediately",
|
|
1121
|
+
description: "Immediately execute a task on a running agent, regardless of its schedule.",
|
|
1122
|
+
parameters: [
|
|
1123
|
+
{ name: "agentId", in: "path", required: true, schema: { type: "string" }, description: "Agent ID" },
|
|
1124
|
+
{ name: "taskId", in: "path", required: true, schema: { type: "string" }, description: "Task ID to execute" },
|
|
1125
|
+
],
|
|
1126
|
+
responses: {
|
|
1127
|
+
"200": { description: "Task execution started" },
|
|
1128
|
+
"400": { description: "Agent is not running" },
|
|
1129
|
+
"404": { description: "Agent or task not found" },
|
|
1130
|
+
},
|
|
1131
|
+
},
|
|
1036
1132
|
},
|
|
1037
1133
|
"/mcp/servers": {
|
|
1038
1134
|
get: {
|
package/src/providers.ts
CHANGED
|
@@ -12,8 +12,9 @@ export const PROVIDERS = {
|
|
|
12
12
|
docsUrl: "https://console.anthropic.com/settings/keys",
|
|
13
13
|
testEndpoint: "https://api.anthropic.com/v1/messages",
|
|
14
14
|
models: [
|
|
15
|
-
{ value: "claude-sonnet-4-
|
|
16
|
-
{ value: "claude-
|
|
15
|
+
{ value: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", recommended: true, input_cost: 3, output_cost: 15 },
|
|
16
|
+
{ value: "claude-sonnet-4-5", label: "Claude Sonnet 4.5", input_cost: 3, output_cost: 15 },
|
|
17
|
+
{ value: "claude-haiku-4-5", label: "Claude Haiku 4.5 (Fast)", input_cost: 0.8, output_cost: 4 },
|
|
17
18
|
],
|
|
18
19
|
},
|
|
19
20
|
openai: {
|
|
@@ -25,8 +26,8 @@ export const PROVIDERS = {
|
|
|
25
26
|
docsUrl: "https://platform.openai.com/api-keys",
|
|
26
27
|
testEndpoint: "https://api.openai.com/v1/models",
|
|
27
28
|
models: [
|
|
28
|
-
{ value: "gpt-4o", label: "GPT-4o", recommended: true },
|
|
29
|
-
{ value: "gpt-4o-mini", label: "GPT-4o Mini (Fast)" },
|
|
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 },
|
|
30
31
|
],
|
|
31
32
|
},
|
|
32
33
|
groq: {
|
|
@@ -38,8 +39,8 @@ export const PROVIDERS = {
|
|
|
38
39
|
docsUrl: "https://console.groq.com/keys",
|
|
39
40
|
testEndpoint: "https://api.groq.com/openai/v1/models",
|
|
40
41
|
models: [
|
|
41
|
-
{ value: "llama-3.3-70b-versatile", label: "Llama 3.3 70B", recommended: true },
|
|
42
|
-
{ value: "llama-3.1-8b-instant", label: "Llama 3.1 8B (Fast)" },
|
|
42
|
+
{ value: "llama-3.3-70b-versatile", label: "Llama 3.3 70B", recommended: true, input_cost: 0, output_cost: 0 },
|
|
43
|
+
{ value: "llama-3.1-8b-instant", label: "Llama 3.1 8B (Fast)", input_cost: 0, output_cost: 0 },
|
|
43
44
|
],
|
|
44
45
|
},
|
|
45
46
|
gemini: {
|
|
@@ -51,8 +52,8 @@ export const PROVIDERS = {
|
|
|
51
52
|
docsUrl: "https://aistudio.google.com/app/apikey",
|
|
52
53
|
testEndpoint: "https://generativelanguage.googleapis.com/v1/models",
|
|
53
54
|
models: [
|
|
54
|
-
{ value: "gemini-3-pro-preview", label: "Gemini 3 Pro Preview (Latest)", recommended: true },
|
|
55
|
-
{ value: "gemini-3-flash-preview", label: "Gemini 3 Flash Preview (Fast)" },
|
|
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 },
|
|
56
57
|
],
|
|
57
58
|
},
|
|
58
59
|
xai: {
|
|
@@ -64,8 +65,8 @@ export const PROVIDERS = {
|
|
|
64
65
|
docsUrl: "https://console.x.ai/",
|
|
65
66
|
testEndpoint: "https://api.x.ai/v1/models",
|
|
66
67
|
models: [
|
|
67
|
-
{ value: "grok-2", label: "Grok 2", recommended: true },
|
|
68
|
-
{ value: "grok-2-mini", label: "Grok 2 Mini (Fast)" },
|
|
68
|
+
{ value: "grok-2", label: "Grok 2", recommended: true, input_cost: 2, output_cost: 10 },
|
|
69
|
+
{ value: "grok-2-mini", label: "Grok 2 Mini (Fast)", input_cost: 0.3, output_cost: 1 },
|
|
69
70
|
],
|
|
70
71
|
},
|
|
71
72
|
together: {
|
|
@@ -77,8 +78,8 @@ export const PROVIDERS = {
|
|
|
77
78
|
docsUrl: "https://api.together.xyz/settings/api-keys",
|
|
78
79
|
testEndpoint: "https://api.together.xyz/v1/models",
|
|
79
80
|
models: [
|
|
80
|
-
{ value: "moonshotai/Kimi-K2.5", label: "Kimi K2.5", recommended: true },
|
|
81
|
-
{ value: "moonshotai/Kimi-K2-Thinking", label: "Kimi K2 Thinking (Reasoning)" },
|
|
81
|
+
{ value: "moonshotai/Kimi-K2.5", label: "Kimi K2.5", recommended: true, input_cost: 1, output_cost: 4 },
|
|
82
|
+
{ value: "moonshotai/Kimi-K2-Thinking", label: "Kimi K2 Thinking (Reasoning)", input_cost: 1, output_cost: 4 },
|
|
82
83
|
],
|
|
83
84
|
},
|
|
84
85
|
fireworks: {
|
|
@@ -90,10 +91,10 @@ export const PROVIDERS = {
|
|
|
90
91
|
docsUrl: "https://fireworks.ai/api-keys",
|
|
91
92
|
testEndpoint: "https://api.fireworks.ai/inference/v1/models",
|
|
92
93
|
models: [
|
|
93
|
-
{ value: "accounts/fireworks/models/kimi-k2p5", label: "Kimi K2.5", recommended: true },
|
|
94
|
-
{ value: "accounts/fireworks/models/kimi-k2-thinking", label: "Kimi K2 Thinking (Reasoning)" },
|
|
95
|
-
{ value: "accounts/fireworks/models/minimax-m2p5", label: "MiniMax M2.5" },
|
|
96
|
-
{ value: "accounts/fireworks/models/glm-5", label: "GLM 5" },
|
|
94
|
+
{ value: "accounts/fireworks/models/kimi-k2p5", label: "Kimi K2.5", recommended: true, input_cost: 1, output_cost: 4 },
|
|
95
|
+
{ value: "accounts/fireworks/models/kimi-k2-thinking", label: "Kimi K2 Thinking (Reasoning)", input_cost: 1, output_cost: 4 },
|
|
96
|
+
{ value: "accounts/fireworks/models/minimax-m2p5", label: "MiniMax M2.5", input_cost: 1, output_cost: 4 },
|
|
97
|
+
{ value: "accounts/fireworks/models/glm-5", label: "GLM 5", input_cost: 1, output_cost: 4 },
|
|
97
98
|
],
|
|
98
99
|
},
|
|
99
100
|
moonshot: {
|
|
@@ -105,8 +106,24 @@ export const PROVIDERS = {
|
|
|
105
106
|
docsUrl: "https://platform.moonshot.cn/console/api-keys",
|
|
106
107
|
testEndpoint: "https://api.moonshot.cn/v1/models",
|
|
107
108
|
models: [
|
|
108
|
-
{ value: "moonshot-v1-128k", label: "Kimi 128K", recommended: true },
|
|
109
|
-
{ value: "moonshot-v1-32k", label: "Kimi 32K (Fast)" },
|
|
109
|
+
{ value: "moonshot-v1-128k", label: "Kimi 128K", recommended: true, input_cost: 1, output_cost: 4 },
|
|
110
|
+
{ value: "moonshot-v1-32k", label: "Kimi 32K (Fast)", input_cost: 0.5, output_cost: 2 },
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
venice: {
|
|
114
|
+
id: "venice",
|
|
115
|
+
name: "Venice",
|
|
116
|
+
displayName: "Venice AI",
|
|
117
|
+
type: "llm" as const,
|
|
118
|
+
envVar: "VENICE_API_KEY",
|
|
119
|
+
docsUrl: "https://docs.venice.ai/overview/pricing",
|
|
120
|
+
testEndpoint: "https://api.venice.ai/api/v1/models",
|
|
121
|
+
models: [
|
|
122
|
+
{ value: "llama-3.3-70b", label: "Llama 3.3 70B", recommended: true, input_cost: 0.7, output_cost: 2.8 },
|
|
123
|
+
{ value: "olafangensan-glm-4.7-flash-heretic", label: "GLM 4.7 Flash Heretic", input_cost: 0.14, output_cost: 0.8 },
|
|
124
|
+
{ value: "qwen3-235b-a22b-instruct-2507", label: "Qwen 3 235B Instruct", input_cost: 0.15, output_cost: 0.75 },
|
|
125
|
+
{ value: "deepseek-v3.2", label: "DeepSeek V3.2", input_cost: 0.4, output_cost: 1 },
|
|
126
|
+
{ value: "venice-uncensored", label: "Venice Uncensored 1.1", input_cost: 0.2, output_cost: 0.9 },
|
|
110
127
|
],
|
|
111
128
|
},
|
|
112
129
|
ollama: {
|
|
@@ -121,11 +138,11 @@ export const PROVIDERS = {
|
|
|
121
138
|
defaultBaseUrl: "http://localhost:11434",
|
|
122
139
|
models: [
|
|
123
140
|
// Default models - actual list fetched dynamically from Ollama
|
|
124
|
-
{ value: "llama3.3", label: "Llama 3.3 (70B)", recommended: true },
|
|
125
|
-
{ value: "llama3.2", label: "Llama 3.2 (3B)" },
|
|
126
|
-
{ value: "qwen2.5", label: "Qwen 2.5" },
|
|
127
|
-
{ value: "mistral", label: "Mistral" },
|
|
128
|
-
{ value: "deepseek-r1", label: "DeepSeek R1" },
|
|
141
|
+
{ value: "llama3.3", label: "Llama 3.3 (70B)", recommended: true, input_cost: 0, output_cost: 0 },
|
|
142
|
+
{ value: "llama3.2", label: "Llama 3.2 (3B)", input_cost: 0, output_cost: 0 },
|
|
143
|
+
{ value: "qwen2.5", label: "Qwen 2.5", input_cost: 0, output_cost: 0 },
|
|
144
|
+
{ value: "mistral", label: "Mistral", input_cost: 0, output_cost: 0 },
|
|
145
|
+
{ value: "deepseek-r1", label: "DeepSeek R1", input_cost: 0, output_cost: 0 },
|
|
129
146
|
],
|
|
130
147
|
},
|
|
131
148
|
// Browser Providers
|
|
@@ -152,22 +169,21 @@ export const PROVIDERS = {
|
|
|
152
169
|
browserengine: {
|
|
153
170
|
id: "browserengine",
|
|
154
171
|
name: "BrowserEngine",
|
|
155
|
-
displayName: "BrowserEngine
|
|
172
|
+
displayName: "BrowserEngine",
|
|
156
173
|
type: "browser" as const,
|
|
157
|
-
envVar: "
|
|
158
|
-
docsUrl: "",
|
|
159
|
-
description: "
|
|
160
|
-
isLocal: true,
|
|
174
|
+
envVar: "BROWSERENGINE_API_KEY",
|
|
175
|
+
docsUrl: "https://browserengine.co",
|
|
176
|
+
description: "Cloud browser automation with stealth browsing and proxies",
|
|
161
177
|
models: [],
|
|
162
178
|
},
|
|
163
|
-
|
|
164
|
-
id: "
|
|
165
|
-
name: "
|
|
166
|
-
displayName: "
|
|
179
|
+
cdp: {
|
|
180
|
+
id: "cdp",
|
|
181
|
+
name: "CDP",
|
|
182
|
+
displayName: "Direct CDP",
|
|
167
183
|
type: "browser" as const,
|
|
168
|
-
envVar: "
|
|
184
|
+
envVar: "CDP_URL",
|
|
169
185
|
docsUrl: "",
|
|
170
|
-
description: "
|
|
186
|
+
description: "Connect directly to any browser via Chrome DevTools Protocol",
|
|
171
187
|
isLocal: true,
|
|
172
188
|
models: [],
|
|
173
189
|
},
|
|
@@ -217,6 +233,16 @@ export const PROVIDERS = {
|
|
|
217
233
|
|
|
218
234
|
export type ProviderId = keyof typeof PROVIDERS;
|
|
219
235
|
|
|
236
|
+
/** Get cost per 1M tokens for a given provider + model. Returns { input_cost, output_cost } in USD. */
|
|
237
|
+
export function getModelCost(provider: string, model: string): { input_cost: number; output_cost: number } {
|
|
238
|
+
const providerDef = PROVIDERS[provider as ProviderId];
|
|
239
|
+
if (!providerDef || !("models" in providerDef)) return { input_cost: 0, output_cost: 0 };
|
|
240
|
+
const modelDef = (providerDef.models as ReadonlyArray<{ value: string; input_cost?: number; output_cost?: number }>)
|
|
241
|
+
.find(m => m.value === model);
|
|
242
|
+
if (!modelDef) return { input_cost: 0, output_cost: 0 };
|
|
243
|
+
return { input_cost: modelDef.input_cost ?? 0, output_cost: modelDef.output_cost ?? 0 };
|
|
244
|
+
}
|
|
245
|
+
|
|
220
246
|
// Provider Keys Management
|
|
221
247
|
export const ProviderKeys = {
|
|
222
248
|
// Save an API key (encrypts before storing)
|
|
@@ -3,7 +3,7 @@ import { join } from "path";
|
|
|
3
3
|
import { homedir } from "os";
|
|
4
4
|
import { mkdirSync, existsSync, rmSync } from "fs";
|
|
5
5
|
import { agentProcesses, agentsStarting, getBinaryPathForAgent, getBinaryStatus, BIN_DIR, telemetryBroadcaster, isShuttingDown, type TelemetryEvent } from "../../server";
|
|
6
|
-
import { AgentDB, McpServerDB, SkillDB, TelemetryDB, generateId, getMultiAgentConfig, getOperatorConfig, type Agent, type Project } from "../../db";
|
|
6
|
+
import { AgentDB, McpServerDB, SkillDB, SubscriptionDB, TelemetryDB, generateId, getMultiAgentConfig, getOperatorConfig, type Agent, type Project } from "../../db";
|
|
7
7
|
import { ProviderKeys, PROVIDERS, type ProviderId } from "../../providers";
|
|
8
8
|
import { binaryExists } from "../../binary";
|
|
9
9
|
|
|
@@ -93,32 +93,29 @@ function buildOperatorConfig(features: Agent["features"], projectId: string | nu
|
|
|
93
93
|
if (!opConfig.enabled) {
|
|
94
94
|
return {
|
|
95
95
|
enabled: false,
|
|
96
|
-
virtual_browser: "http://localhost:8098",
|
|
97
96
|
display_width: 1024,
|
|
98
97
|
display_height: 768,
|
|
99
98
|
max_actions_per_turn: 5,
|
|
100
99
|
};
|
|
101
100
|
}
|
|
102
101
|
|
|
103
|
-
const browserProvider = opConfig.browser_provider || "";
|
|
102
|
+
const browserProvider = opConfig.browser_provider || "browserengine";
|
|
104
103
|
const displayWidth = opConfig.display_width || 1024;
|
|
105
104
|
const displayHeight = opConfig.display_height || 768;
|
|
106
105
|
const maxActions = opConfig.max_actions_per_turn || 5;
|
|
107
106
|
|
|
108
|
-
// Map browser provider IDs to agent binary config
|
|
109
107
|
const operatorResult: Record<string, unknown> = {
|
|
110
108
|
enabled: true,
|
|
109
|
+
browser_provider: browserProvider,
|
|
111
110
|
display_width: displayWidth,
|
|
112
111
|
display_height: displayHeight,
|
|
113
112
|
max_actions_per_turn: maxActions,
|
|
114
113
|
};
|
|
115
114
|
|
|
115
|
+
// Only include the active provider's config
|
|
116
116
|
if (browserProvider === "browserbase") {
|
|
117
117
|
const raw = ProviderKeys.getDecryptedForProject("browserbase", projectId);
|
|
118
|
-
operatorResult.browser_provider = "browserbase";
|
|
119
|
-
operatorResult.virtual_browser = "http://localhost:8098"; // fallback
|
|
120
118
|
if (raw) {
|
|
121
|
-
// Parse JSON object {api_key, project_id} or plain string (backwards compat)
|
|
122
119
|
try {
|
|
123
120
|
const parsed = JSON.parse(raw);
|
|
124
121
|
operatorResult.browserbase = { api_key: parsed.api_key || raw, project_id: parsed.project_id || "" };
|
|
@@ -128,26 +125,21 @@ function buildOperatorConfig(features: Agent["features"], projectId: string | nu
|
|
|
128
125
|
}
|
|
129
126
|
} else if (browserProvider === "steel") {
|
|
130
127
|
const apiKey = ProviderKeys.getDecryptedForProject("steel", projectId);
|
|
131
|
-
operatorResult.browser_provider = "steel";
|
|
132
|
-
operatorResult.virtual_browser = "http://localhost:8098"; // fallback
|
|
133
128
|
if (apiKey) {
|
|
134
129
|
operatorResult.steel = { api_key: apiKey, base_url: "https://api.steel.dev" };
|
|
135
130
|
}
|
|
136
|
-
} else if (browserProvider === "
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
operatorResult.chrome = { debug_url: debugUrl };
|
|
131
|
+
} else if (browserProvider === "cdp") {
|
|
132
|
+
// CDP uses a URL, not an API key — stored as the provider key value
|
|
133
|
+
const cdpUrl = ProviderKeys.getDecryptedForProject("cdp", projectId);
|
|
134
|
+
if (cdpUrl) {
|
|
135
|
+
operatorResult.cdp = { url: cdpUrl };
|
|
142
136
|
}
|
|
143
|
-
} else if (browserProvider === "browserengine") {
|
|
144
|
-
const url = ProviderKeys.getDecryptedForProject("browserengine", projectId);
|
|
145
|
-
operatorResult.browser_provider = "self";
|
|
146
|
-
operatorResult.virtual_browser = url || "http://localhost:8098";
|
|
147
137
|
} else {
|
|
148
|
-
// Default:
|
|
149
|
-
|
|
150
|
-
|
|
138
|
+
// Default: browserengine
|
|
139
|
+
const apiKey = ProviderKeys.getDecryptedForProject("browserengine", projectId);
|
|
140
|
+
if (apiKey) {
|
|
141
|
+
operatorResult.browserengine = { api_key: apiKey, base_url: "https://api.browserengine.co" };
|
|
142
|
+
}
|
|
151
143
|
}
|
|
152
144
|
|
|
153
145
|
return operatorResult;
|
|
@@ -173,8 +165,12 @@ export function buildAgentConfig(agent: Agent, providerKey: string) {
|
|
|
173
165
|
enabled: boolean;
|
|
174
166
|
}> = [];
|
|
175
167
|
|
|
168
|
+
// Batch load skills and MCP servers (2 queries instead of N+M)
|
|
169
|
+
const skillMap = SkillDB.findByIds(agent.skills || []);
|
|
170
|
+
const mcpMap = McpServerDB.findByIds(agent.mcp_servers || []);
|
|
171
|
+
|
|
176
172
|
for (const skillId of agent.skills || []) {
|
|
177
|
-
const skill =
|
|
173
|
+
const skill = skillMap.get(skillId);
|
|
178
174
|
if (!skill || !skill.enabled) continue;
|
|
179
175
|
|
|
180
176
|
skillDefinitions.push({
|
|
@@ -190,7 +186,7 @@ export function buildAgentConfig(agent: Agent, providerKey: string) {
|
|
|
190
186
|
}
|
|
191
187
|
|
|
192
188
|
for (const id of agent.mcp_servers || []) {
|
|
193
|
-
const server =
|
|
189
|
+
const server = mcpMap.get(id);
|
|
194
190
|
if (!server) continue;
|
|
195
191
|
|
|
196
192
|
if (server.type === "local" && server.status === "running") {
|
|
@@ -341,7 +337,6 @@ export function buildAgentConfig(agent: Agent, providerKey: string) {
|
|
|
341
337
|
const baseUrl = process.env.PUBLIC_URL || `http://localhost:${process.env.PORT || 4280}`;
|
|
342
338
|
return {
|
|
343
339
|
enabled: multiAgentConfig.enabled,
|
|
344
|
-
mode: multiAgentConfig.mode || "worker",
|
|
345
340
|
group: multiAgentConfig.group || agent.project_id || undefined,
|
|
346
341
|
// This agent's reachable URL for peer communication
|
|
347
342
|
url: `http://localhost:${agent.port}`,
|
|
@@ -387,9 +382,8 @@ export async function pushSkillsToAgent(agentId: string, port: number, skills: A
|
|
|
387
382
|
}
|
|
388
383
|
|
|
389
384
|
try {
|
|
390
|
-
// Push
|
|
391
|
-
|
|
392
|
-
// First try PUT to update existing skill
|
|
385
|
+
// Push all skills in parallel - try PUT first (update), then POST (create) if not found
|
|
386
|
+
await Promise.allSettled(skills.map(async (skill) => {
|
|
393
387
|
let res = await agentFetch(agentId, port, "/skills", {
|
|
394
388
|
method: "PUT",
|
|
395
389
|
headers: { "Content-Type": "application/json" },
|
|
@@ -397,7 +391,6 @@ export async function pushSkillsToAgent(agentId: string, port: number, skills: A
|
|
|
397
391
|
signal: AbortSignal.timeout(5000),
|
|
398
392
|
});
|
|
399
393
|
|
|
400
|
-
// If skill doesn't exist (404), create it with POST
|
|
401
394
|
if (res.status === 404) {
|
|
402
395
|
res = await agentFetch(agentId, port, "/skills", {
|
|
403
396
|
method: "POST",
|
|
@@ -411,7 +404,7 @@ export async function pushSkillsToAgent(agentId: string, port: number, skills: A
|
|
|
411
404
|
const data = await res.json().catch(() => ({}));
|
|
412
405
|
console.error(`[pushSkillsToAgent] Failed to push skill ${skill.name}:`, data.error || res.status);
|
|
413
406
|
}
|
|
414
|
-
}
|
|
407
|
+
}));
|
|
415
408
|
|
|
416
409
|
// Enable skills globally via POST /skills/status
|
|
417
410
|
const statusRes = await agentFetch(agentId, port, "/skills/status", {
|
|
@@ -611,6 +604,8 @@ export async function startAgentProcess(
|
|
|
611
604
|
console.log(` Pushing configuration...`);
|
|
612
605
|
}
|
|
613
606
|
const config = buildAgentConfig(agent, providerKey);
|
|
607
|
+
console.log(`[DEBUG] operator config being pushed:`, JSON.stringify(config.operator, null, 2));
|
|
608
|
+
console.log(`[DEBUG] builtin_tools:`, JSON.stringify(config.operator?.builtin_tools));
|
|
614
609
|
const configResult = await pushConfigToAgent(agent.id, port, config);
|
|
615
610
|
if (!configResult.success) {
|
|
616
611
|
if (!silent) {
|
|
@@ -650,24 +645,27 @@ export async function startAgentProcess(
|
|
|
650
645
|
}
|
|
651
646
|
|
|
652
647
|
// Transform DB agent to API response format (camelCase for frontend compatibility)
|
|
648
|
+
// Uses batch queries + light MCP loading (no decryption) for performance
|
|
653
649
|
export function toApiAgent(agent: Agent) {
|
|
654
|
-
//
|
|
650
|
+
// Batch load MCP servers (light = no decryption) and skills in 2 queries
|
|
651
|
+
const mcpMap = McpServerDB.findByIdsLight(agent.mcp_servers || []);
|
|
652
|
+
const skillMap = SkillDB.findByIds(agent.skills || []);
|
|
653
|
+
|
|
655
654
|
const mcpServerDetails = (agent.mcp_servers || [])
|
|
656
|
-
.map(id =>
|
|
657
|
-
.filter((s): s is NonNullable<typeof s> => s
|
|
655
|
+
.map(id => mcpMap.get(id))
|
|
656
|
+
.filter((s): s is NonNullable<typeof s> => !!s)
|
|
658
657
|
.map(s => ({
|
|
659
658
|
id: s.id,
|
|
660
659
|
name: s.name,
|
|
661
660
|
type: s.type,
|
|
662
661
|
status: s.status,
|
|
663
662
|
port: s.port,
|
|
664
|
-
url: s.url,
|
|
663
|
+
url: s.url,
|
|
665
664
|
}));
|
|
666
665
|
|
|
667
|
-
// Look up skill details
|
|
668
666
|
const skillDetails = (agent.skills || [])
|
|
669
|
-
.map(id =>
|
|
670
|
-
.filter((s): s is NonNullable<typeof s> => s
|
|
667
|
+
.map(id => skillMap.get(id))
|
|
668
|
+
.filter((s): s is NonNullable<typeof s> => !!s)
|
|
671
669
|
.map(s => ({
|
|
672
670
|
id: s.id,
|
|
673
671
|
name: s.name,
|
|
@@ -676,6 +674,11 @@ export function toApiAgent(agent: Agent) {
|
|
|
676
674
|
enabled: s.enabled,
|
|
677
675
|
}));
|
|
678
676
|
|
|
677
|
+
// Look up subscriptions
|
|
678
|
+
const subscriptions = SubscriptionDB.findByAgentId(agent.id).map(s => ({
|
|
679
|
+
id: s.id, trigger_slug: s.trigger_slug, enabled: s.enabled,
|
|
680
|
+
}));
|
|
681
|
+
|
|
679
682
|
return {
|
|
680
683
|
id: agent.id,
|
|
681
684
|
name: agent.name,
|
|
@@ -685,29 +688,33 @@ export function toApiAgent(agent: Agent) {
|
|
|
685
688
|
status: agent.status,
|
|
686
689
|
port: agent.port,
|
|
687
690
|
features: agent.features,
|
|
688
|
-
mcpServers: agent.mcp_servers,
|
|
689
|
-
mcpServerDetails,
|
|
690
|
-
skills: agent.skills,
|
|
691
|
-
skillDetails,
|
|
691
|
+
mcpServers: agent.mcp_servers,
|
|
692
|
+
mcpServerDetails,
|
|
693
|
+
skills: agent.skills,
|
|
694
|
+
skillDetails,
|
|
695
|
+
subscriptions,
|
|
692
696
|
projectId: agent.project_id,
|
|
693
697
|
createdAt: agent.created_at,
|
|
694
698
|
updatedAt: agent.updated_at,
|
|
695
699
|
};
|
|
696
700
|
}
|
|
697
701
|
|
|
698
|
-
// Batch transform: fetch all MCP servers + skills in
|
|
702
|
+
// Batch transform: fetch all MCP servers + skills + subscriptions in 3 queries instead of N per agent
|
|
699
703
|
export function toApiAgentsBatch(agents: Agent[]) {
|
|
700
704
|
// Collect all unique IDs
|
|
701
705
|
const allMcpIds = new Set<string>();
|
|
702
706
|
const allSkillIds = new Set<string>();
|
|
707
|
+
const allAgentIds: string[] = [];
|
|
703
708
|
for (const agent of agents) {
|
|
709
|
+
allAgentIds.push(agent.id);
|
|
704
710
|
for (const id of agent.mcp_servers || []) allMcpIds.add(id);
|
|
705
711
|
for (const id of agent.skills || []) allSkillIds.add(id);
|
|
706
712
|
}
|
|
707
713
|
|
|
708
|
-
// Batch load in
|
|
709
|
-
const mcpMap = McpServerDB.
|
|
714
|
+
// Batch load in 3 queries (Light = no decryption for MCP servers)
|
|
715
|
+
const mcpMap = McpServerDB.findByIdsLight([...allMcpIds]);
|
|
710
716
|
const skillMap = SkillDB.findByIds([...allSkillIds]);
|
|
717
|
+
const subsMap = SubscriptionDB.findByAgentIds(allAgentIds);
|
|
711
718
|
|
|
712
719
|
return agents.map(agent => {
|
|
713
720
|
const mcpServerDetails = (agent.mcp_servers || [])
|
|
@@ -720,11 +727,15 @@ export function toApiAgentsBatch(agents: Agent[]) {
|
|
|
720
727
|
.filter((s): s is NonNullable<typeof s> => !!s)
|
|
721
728
|
.map(s => ({ id: s.id, name: s.name, description: s.description, version: s.version, enabled: s.enabled }));
|
|
722
729
|
|
|
730
|
+
const subscriptions = (subsMap.get(agent.id) || []).map(s => ({
|
|
731
|
+
id: s.id, trigger_slug: s.trigger_slug, enabled: s.enabled,
|
|
732
|
+
}));
|
|
733
|
+
|
|
723
734
|
return {
|
|
724
735
|
id: agent.id, name: agent.name, model: agent.model, provider: agent.provider,
|
|
725
736
|
systemPrompt: agent.system_prompt, status: agent.status, port: agent.port,
|
|
726
737
|
features: agent.features, mcpServers: agent.mcp_servers, mcpServerDetails,
|
|
727
|
-
skills: agent.skills, skillDetails, projectId: agent.project_id,
|
|
738
|
+
skills: agent.skills, skillDetails, subscriptions, projectId: agent.project_id,
|
|
728
739
|
createdAt: agent.created_at, updatedAt: agent.updated_at,
|
|
729
740
|
};
|
|
730
741
|
});
|
|
@@ -742,11 +753,12 @@ export function toApiProject(project: Project) {
|
|
|
742
753
|
};
|
|
743
754
|
}
|
|
744
755
|
|
|
745
|
-
// Helper to fetch from a running agent (with authentication)
|
|
746
|
-
export async function fetchFromAgent(agentId: string, port: number, endpoint: string): Promise<any> {
|
|
756
|
+
// Helper to fetch from a running agent (with authentication + timeout)
|
|
757
|
+
export async function fetchFromAgent(agentId: string, port: number, endpoint: string, timeoutMs = 3000): Promise<any> {
|
|
747
758
|
try {
|
|
748
759
|
const response = await agentFetch(agentId, port, endpoint, {
|
|
749
760
|
headers: { "Accept": "application/json" },
|
|
761
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
750
762
|
});
|
|
751
763
|
if (response.ok) {
|
|
752
764
|
return await response.json();
|
package/src/routes/api/agents.ts
CHANGED
|
@@ -62,7 +62,7 @@ export async function handleAgentRoutes(
|
|
|
62
62
|
const agent = AgentDB.create({
|
|
63
63
|
id: generateId(),
|
|
64
64
|
name,
|
|
65
|
-
model: model || "claude-sonnet-4-
|
|
65
|
+
model: model || "claude-sonnet-4-6",
|
|
66
66
|
provider: provider || "anthropic",
|
|
67
67
|
system_prompt: systemPrompt || "You are a helpful assistant.",
|
|
68
68
|
features: features || DEFAULT_FEATURES,
|
|
@@ -270,6 +270,25 @@ export async function handleAgentRoutes(
|
|
|
270
270
|
});
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
+
// ==================== SHARE LINK ====================
|
|
274
|
+
|
|
275
|
+
// GET /api/agents/:id/share-token - Get the share token for this agent
|
|
276
|
+
const shareTokenMatch = path.match(/^\/api\/agents\/([^/]+)\/share-token$/);
|
|
277
|
+
if (shareTokenMatch && method === "GET") {
|
|
278
|
+
const agent = AgentDB.findById(shareTokenMatch[1]);
|
|
279
|
+
if (!agent) {
|
|
280
|
+
return json({ error: "Agent not found" }, 404);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const { getShareToken } = await import("../share");
|
|
284
|
+
const token = getShareToken(agent.id);
|
|
285
|
+
if (!token) {
|
|
286
|
+
return json({ error: "Could not generate share token" }, 500);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return json({ token });
|
|
290
|
+
}
|
|
291
|
+
|
|
273
292
|
// ==================== AGENT LIFECYCLE ====================
|
|
274
293
|
|
|
275
294
|
// POST /api/agents/:id/start - Start an agent
|
|
@@ -358,6 +377,7 @@ export async function handleAgentRoutes(
|
|
|
358
377
|
try {
|
|
359
378
|
const body = await req.json();
|
|
360
379
|
|
|
380
|
+
|
|
361
381
|
// Proxy to the agent's /chat endpoint with authentication
|
|
362
382
|
const response = await agentFetch(agent.id, agent.port, "/chat", {
|
|
363
383
|
method: "POST",
|
|
@@ -451,6 +471,56 @@ export async function handleAgentRoutes(
|
|
|
451
471
|
|
|
452
472
|
// ==================== THREAD & MESSAGE PROXY ====================
|
|
453
473
|
|
|
474
|
+
// GET /api/threads - Consolidated threads from all running agents
|
|
475
|
+
if (path === "/api/threads" && method === "GET") {
|
|
476
|
+
const url = new URL(req.url);
|
|
477
|
+
const projectId = url.searchParams.get("project_id");
|
|
478
|
+
|
|
479
|
+
let agents;
|
|
480
|
+
if (projectId === "unassigned") {
|
|
481
|
+
agents = AgentDB.findByProject(null);
|
|
482
|
+
} else if (projectId) {
|
|
483
|
+
agents = AgentDB.findByProject(projectId);
|
|
484
|
+
} else {
|
|
485
|
+
agents = AgentDB.findAll();
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Only query running agents (excluding meta agent)
|
|
489
|
+
const runningAgents = agents.filter(a => a.id !== META_AGENT_ID && a.status === "running" && a.port);
|
|
490
|
+
|
|
491
|
+
const results = await Promise.allSettled(
|
|
492
|
+
runningAgents.map(async (agent) => {
|
|
493
|
+
const response = await agentFetch(agent.id, agent.port!, "/threads", {
|
|
494
|
+
method: "GET",
|
|
495
|
+
headers: { "Accept": "application/json" },
|
|
496
|
+
signal: AbortSignal.timeout(3000),
|
|
497
|
+
});
|
|
498
|
+
if (!response.ok) return [];
|
|
499
|
+
const data = await response.json() as any;
|
|
500
|
+
const threads = Array.isArray(data) ? data : (data.threads ?? []);
|
|
501
|
+
return threads.map((t: any) => ({
|
|
502
|
+
...t,
|
|
503
|
+
agent_id: agent.id,
|
|
504
|
+
agent_name: agent.name,
|
|
505
|
+
}));
|
|
506
|
+
})
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
const allThreads = results
|
|
510
|
+
.filter((r): r is PromiseFulfilledResult<any[]> => r.status === "fulfilled")
|
|
511
|
+
.flatMap(r => r.value)
|
|
512
|
+
.filter((t: any) => !t.parent_id);
|
|
513
|
+
|
|
514
|
+
// Sort by most recent first
|
|
515
|
+
allThreads.sort((a, b) => {
|
|
516
|
+
const ta = a.updated_at || a.created_at || "";
|
|
517
|
+
const tb = b.updated_at || b.created_at || "";
|
|
518
|
+
return tb.localeCompare(ta);
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
return json({ threads: allThreads });
|
|
522
|
+
}
|
|
523
|
+
|
|
454
524
|
// GET/POST /api/agents/:id/threads
|
|
455
525
|
const threadsListMatch = path.match(/^\/api\/agents\/([^/]+)\/threads$/);
|
|
456
526
|
if (threadsListMatch && method === "GET") {
|
|
@@ -789,7 +859,6 @@ export async function handleAgentRoutes(
|
|
|
789
859
|
id: a.id,
|
|
790
860
|
name: a.name,
|
|
791
861
|
url: `http://localhost:${a.port}`,
|
|
792
|
-
mode: agentConfig.mode || "worker",
|
|
793
862
|
group: agentConfig.group || a.project_id,
|
|
794
863
|
};
|
|
795
864
|
});
|