@axiom-lattice/gateway 2.1.12 → 2.1.13
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/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +10 -0
- package/README.md +53 -0
- package/dist/index.js +379 -50
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +377 -48
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/config.ts +124 -0
- package/src/controllers/config.ts +126 -0
- package/src/controllers/models.ts +152 -0
- package/src/routes/index.ts +26 -0
- package/src/schemas/index.ts +74 -0
- package/src/services/supabase.ts +44 -10
package/dist/index.mjs
CHANGED
|
@@ -927,6 +927,300 @@ async function deleteThread(request, reply) {
|
|
|
927
927
|
};
|
|
928
928
|
}
|
|
929
929
|
|
|
930
|
+
// src/config.ts
|
|
931
|
+
var ConfigService = class {
|
|
932
|
+
constructor() {
|
|
933
|
+
this.config = this.loadFromEnv();
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Load configuration from environment variables
|
|
937
|
+
*/
|
|
938
|
+
loadFromEnv() {
|
|
939
|
+
return {
|
|
940
|
+
port: process.env.PORT ? Number(process.env.PORT) : void 0,
|
|
941
|
+
queueServiceType: process.env.QUEUE_SERVICE_TYPE,
|
|
942
|
+
redisUrl: process.env.REDIS_URL,
|
|
943
|
+
redisPassword: process.env.REDIS_PASSWORD,
|
|
944
|
+
queueName: process.env.QUEUE_NAME
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Update configuration from JSON object
|
|
949
|
+
* This will update both the internal config and process.env
|
|
950
|
+
*/
|
|
951
|
+
updateConfig(jsonConfig) {
|
|
952
|
+
for (const [key, value] of Object.entries(jsonConfig)) {
|
|
953
|
+
if (value !== null && value !== void 0) {
|
|
954
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
955
|
+
for (const [nestedKey, nestedValue] of Object.entries(value)) {
|
|
956
|
+
const envKey = `${key.toUpperCase()}_${nestedKey.toUpperCase()}`;
|
|
957
|
+
process.env[envKey] = String(nestedValue);
|
|
958
|
+
}
|
|
959
|
+
} else {
|
|
960
|
+
process.env[key.toUpperCase()] = String(value);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
this.config = this.loadFromEnv();
|
|
965
|
+
this.config = this.deepMerge(this.config, jsonConfig);
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Deep merge two objects
|
|
969
|
+
*/
|
|
970
|
+
deepMerge(target, source) {
|
|
971
|
+
const output = { ...target };
|
|
972
|
+
if (this.isObject(target) && this.isObject(source)) {
|
|
973
|
+
Object.keys(source).forEach((key) => {
|
|
974
|
+
if (this.isObject(source[key])) {
|
|
975
|
+
if (!(key in target)) {
|
|
976
|
+
Object.assign(output, { [key]: source[key] });
|
|
977
|
+
} else {
|
|
978
|
+
output[key] = this.deepMerge(target[key], source[key]);
|
|
979
|
+
}
|
|
980
|
+
} else {
|
|
981
|
+
Object.assign(output, { [key]: source[key] });
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
return output;
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Check if value is a plain object
|
|
989
|
+
*/
|
|
990
|
+
isObject(item) {
|
|
991
|
+
return item && typeof item === "object" && !Array.isArray(item);
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Get current configuration
|
|
995
|
+
*/
|
|
996
|
+
getConfig() {
|
|
997
|
+
return { ...this.config };
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
var configService = new ConfigService();
|
|
1001
|
+
|
|
1002
|
+
// src/services/queue_service.ts
|
|
1003
|
+
import {
|
|
1004
|
+
queueLatticeManager,
|
|
1005
|
+
registerQueueLattice,
|
|
1006
|
+
getQueueLattice
|
|
1007
|
+
} from "@axiom-lattice/core";
|
|
1008
|
+
import { QueueType } from "@axiom-lattice/protocols";
|
|
1009
|
+
import { RedisQueueClient } from "@axiom-lattice/queue-redis";
|
|
1010
|
+
var DEFAULT_QUEUE_KEY = "default";
|
|
1011
|
+
var queueServiceType = process.env.QUEUE_SERVICE_TYPE || "memory";
|
|
1012
|
+
var setQueueServiceType = (type) => {
|
|
1013
|
+
queueServiceType = type;
|
|
1014
|
+
console.log(`Queue service type set to: ${type}`);
|
|
1015
|
+
const queueName = process.env.QUEUE_NAME || "tasks";
|
|
1016
|
+
const config = {
|
|
1017
|
+
name: "Default Queue Service",
|
|
1018
|
+
description: `Default ${type} queue service`,
|
|
1019
|
+
type: type === "redis" ? QueueType.REDIS : QueueType.MEMORY,
|
|
1020
|
+
queueName,
|
|
1021
|
+
options: type === "redis" ? {
|
|
1022
|
+
redisUrl: process.env.REDIS_URL,
|
|
1023
|
+
redisPassword: process.env.REDIS_PASSWORD
|
|
1024
|
+
} : void 0
|
|
1025
|
+
};
|
|
1026
|
+
if (queueLatticeManager.hasLattice(DEFAULT_QUEUE_KEY)) {
|
|
1027
|
+
queueLatticeManager.removeLattice(DEFAULT_QUEUE_KEY);
|
|
1028
|
+
}
|
|
1029
|
+
let client;
|
|
1030
|
+
if (type === "redis") {
|
|
1031
|
+
client = new RedisQueueClient(queueName, {
|
|
1032
|
+
redisUrl: process.env.REDIS_URL,
|
|
1033
|
+
redisPassword: process.env.REDIS_PASSWORD
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
registerQueueLattice(DEFAULT_QUEUE_KEY, config, client);
|
|
1037
|
+
};
|
|
1038
|
+
var getQueueService = () => {
|
|
1039
|
+
if (!queueLatticeManager.hasLattice(DEFAULT_QUEUE_KEY)) {
|
|
1040
|
+
setQueueServiceType(queueServiceType);
|
|
1041
|
+
}
|
|
1042
|
+
return getQueueLattice(DEFAULT_QUEUE_KEY);
|
|
1043
|
+
};
|
|
1044
|
+
var popAgentTaskFromQueue = async () => {
|
|
1045
|
+
const queue = getQueueService();
|
|
1046
|
+
const result = await queue.pop();
|
|
1047
|
+
return result;
|
|
1048
|
+
};
|
|
1049
|
+
|
|
1050
|
+
// src/controllers/config.ts
|
|
1051
|
+
async function updateConfig(request, reply) {
|
|
1052
|
+
try {
|
|
1053
|
+
const { config: jsonConfig } = request.body;
|
|
1054
|
+
if (!jsonConfig || typeof jsonConfig !== "object") {
|
|
1055
|
+
return reply.status(400).send({
|
|
1056
|
+
success: false,
|
|
1057
|
+
error: "Invalid configuration: config must be an object"
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
configService.updateConfig(jsonConfig);
|
|
1061
|
+
const warnings = [];
|
|
1062
|
+
const requiresRestart = [];
|
|
1063
|
+
if (jsonConfig.port !== void 0) {
|
|
1064
|
+
requiresRestart.push("PORT");
|
|
1065
|
+
warnings.push("Port change requires server restart to take effect");
|
|
1066
|
+
}
|
|
1067
|
+
if (jsonConfig.queueServiceType) {
|
|
1068
|
+
setQueueServiceType(jsonConfig.queueServiceType);
|
|
1069
|
+
}
|
|
1070
|
+
if ((jsonConfig.redisUrl || jsonConfig.redisPassword) && (process.env.QUEUE_SERVICE_TYPE === "redis" || jsonConfig.queueServiceType === "redis")) {
|
|
1071
|
+
const currentType = jsonConfig.queueServiceType || process.env.QUEUE_SERVICE_TYPE || "memory";
|
|
1072
|
+
if (currentType === "redis") {
|
|
1073
|
+
setQueueServiceType("redis");
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
const updatedConfig = configService.getConfig();
|
|
1077
|
+
const safeConfig = {
|
|
1078
|
+
...updatedConfig,
|
|
1079
|
+
redisPassword: updatedConfig.redisPassword ? "***" : updatedConfig.redisPassword
|
|
1080
|
+
};
|
|
1081
|
+
return reply.send({
|
|
1082
|
+
success: true,
|
|
1083
|
+
message: "Configuration updated successfully",
|
|
1084
|
+
data: safeConfig,
|
|
1085
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
1086
|
+
requiresRestart: requiresRestart.length > 0 ? requiresRestart : void 0
|
|
1087
|
+
});
|
|
1088
|
+
} catch (error) {
|
|
1089
|
+
console.error("Failed to update configuration", {
|
|
1090
|
+
error: error.message,
|
|
1091
|
+
stack: error.stack
|
|
1092
|
+
});
|
|
1093
|
+
return reply.status(500).send({
|
|
1094
|
+
success: false,
|
|
1095
|
+
error: error.message || "Failed to update configuration"
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
async function getConfig(request, reply) {
|
|
1100
|
+
try {
|
|
1101
|
+
const currentConfig = configService.getConfig();
|
|
1102
|
+
const safeConfig = {
|
|
1103
|
+
...currentConfig,
|
|
1104
|
+
redisPassword: currentConfig.redisPassword ? "***" : currentConfig.redisPassword
|
|
1105
|
+
};
|
|
1106
|
+
return reply.send({
|
|
1107
|
+
success: true,
|
|
1108
|
+
data: safeConfig
|
|
1109
|
+
});
|
|
1110
|
+
} catch (error) {
|
|
1111
|
+
console.error("Failed to get configuration", {
|
|
1112
|
+
error: error.message,
|
|
1113
|
+
stack: error.stack
|
|
1114
|
+
});
|
|
1115
|
+
return reply.status(500).send({
|
|
1116
|
+
success: false,
|
|
1117
|
+
error: error.message || "Failed to get configuration"
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// src/controllers/models.ts
|
|
1123
|
+
import { registerModelLattice, modelLatticeManager } from "@axiom-lattice/core";
|
|
1124
|
+
async function getModels(request, reply) {
|
|
1125
|
+
try {
|
|
1126
|
+
const allLattices = modelLatticeManager.getAllLattices();
|
|
1127
|
+
const models = allLattices.map((lattice) => {
|
|
1128
|
+
const config = lattice.client.config || {};
|
|
1129
|
+
return {
|
|
1130
|
+
key: lattice.key,
|
|
1131
|
+
model: config.model || "",
|
|
1132
|
+
provider: config.provider || "openai",
|
|
1133
|
+
streaming: config.streaming || false,
|
|
1134
|
+
apiKey: config.apiKey || "",
|
|
1135
|
+
baseURL: config.baseURL || "",
|
|
1136
|
+
maxTokens: config.maxTokens,
|
|
1137
|
+
temperature: config.temperature,
|
|
1138
|
+
timeout: config.timeout,
|
|
1139
|
+
maxRetries: config.maxRetries
|
|
1140
|
+
};
|
|
1141
|
+
});
|
|
1142
|
+
return reply.send({
|
|
1143
|
+
success: true,
|
|
1144
|
+
data: models
|
|
1145
|
+
});
|
|
1146
|
+
} catch (error) {
|
|
1147
|
+
console.error("Failed to get models", {
|
|
1148
|
+
error: error.message,
|
|
1149
|
+
stack: error.stack
|
|
1150
|
+
});
|
|
1151
|
+
return reply.status(500).send({
|
|
1152
|
+
success: false,
|
|
1153
|
+
error: error.message || "Failed to get models"
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
async function updateModels(request, reply) {
|
|
1158
|
+
try {
|
|
1159
|
+
const { models } = request.body;
|
|
1160
|
+
if (!models || !Array.isArray(models)) {
|
|
1161
|
+
return reply.status(400).send({
|
|
1162
|
+
success: false,
|
|
1163
|
+
error: "Invalid request: models must be an array"
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
const registeredModels = [];
|
|
1167
|
+
const errors = [];
|
|
1168
|
+
for (const modelConfig of models) {
|
|
1169
|
+
if (!modelConfig.key || !modelConfig.model || !modelConfig.provider) {
|
|
1170
|
+
errors.push(
|
|
1171
|
+
`Model configuration is incomplete: key, model, and provider are required`
|
|
1172
|
+
);
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
try {
|
|
1176
|
+
if (modelLatticeManager.hasLattice(modelConfig.key)) {
|
|
1177
|
+
modelLatticeManager.removeLattice(modelConfig.key);
|
|
1178
|
+
}
|
|
1179
|
+
const llmConfig = {
|
|
1180
|
+
provider: modelConfig.provider,
|
|
1181
|
+
model: modelConfig.model,
|
|
1182
|
+
streaming: modelConfig.streaming ?? false,
|
|
1183
|
+
apiKey: modelConfig.apiKey,
|
|
1184
|
+
baseURL: modelConfig.baseURL,
|
|
1185
|
+
maxTokens: modelConfig.maxTokens,
|
|
1186
|
+
temperature: modelConfig.temperature,
|
|
1187
|
+
timeout: modelConfig.timeout,
|
|
1188
|
+
maxRetries: modelConfig.maxRetries
|
|
1189
|
+
};
|
|
1190
|
+
registerModelLattice(modelConfig.key, llmConfig);
|
|
1191
|
+
registeredModels.push(modelConfig.key);
|
|
1192
|
+
} catch (error) {
|
|
1193
|
+
errors.push(
|
|
1194
|
+
`Failed to register model ${modelConfig.key}: ${error.message}`
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
if (errors.length > 0 && registeredModels.length === 0) {
|
|
1199
|
+
return reply.status(400).send({
|
|
1200
|
+
success: false,
|
|
1201
|
+
error: errors.join("; ")
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
return reply.send({
|
|
1205
|
+
success: true,
|
|
1206
|
+
message: `Successfully registered ${registeredModels.length} model(s)`,
|
|
1207
|
+
data: {
|
|
1208
|
+
registered: registeredModels,
|
|
1209
|
+
errors: errors.length > 0 ? errors : void 0
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
} catch (error) {
|
|
1213
|
+
console.error("Failed to update models", {
|
|
1214
|
+
error: error.message,
|
|
1215
|
+
stack: error.stack
|
|
1216
|
+
});
|
|
1217
|
+
return reply.status(500).send({
|
|
1218
|
+
success: false,
|
|
1219
|
+
error: error.message || "Failed to update models"
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
930
1224
|
// src/schemas/index.ts
|
|
931
1225
|
var getAllMemoryItemsSchema = {
|
|
932
1226
|
description: "Get all memory items for an assistant thread",
|
|
@@ -1095,6 +1389,77 @@ var triggerAgentTaskSchema = {
|
|
|
1095
1389
|
}
|
|
1096
1390
|
}
|
|
1097
1391
|
};
|
|
1392
|
+
var updateConfigSchema = {
|
|
1393
|
+
description: "Update gateway configuration",
|
|
1394
|
+
tags: ["Configuration"],
|
|
1395
|
+
summary: "Update Configuration",
|
|
1396
|
+
body: {
|
|
1397
|
+
type: "object",
|
|
1398
|
+
properties: {
|
|
1399
|
+
config: {
|
|
1400
|
+
type: "object",
|
|
1401
|
+
description: "Configuration object to update",
|
|
1402
|
+
properties: {
|
|
1403
|
+
port: { type: "number", description: "Server port" },
|
|
1404
|
+
queueServiceType: {
|
|
1405
|
+
type: "string",
|
|
1406
|
+
enum: ["memory", "redis"],
|
|
1407
|
+
description: "Queue service type"
|
|
1408
|
+
},
|
|
1409
|
+
redisUrl: { type: "string", description: "Redis URL" },
|
|
1410
|
+
redisPassword: { type: "string", description: "Redis password" },
|
|
1411
|
+
queueName: { type: "string", description: "Queue name" }
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
},
|
|
1415
|
+
required: ["config"]
|
|
1416
|
+
},
|
|
1417
|
+
response: {
|
|
1418
|
+
200: {
|
|
1419
|
+
type: "object",
|
|
1420
|
+
properties: {
|
|
1421
|
+
success: { type: "boolean" },
|
|
1422
|
+
message: { type: "string" },
|
|
1423
|
+
data: { type: "object" }
|
|
1424
|
+
}
|
|
1425
|
+
},
|
|
1426
|
+
400: {
|
|
1427
|
+
type: "object",
|
|
1428
|
+
properties: {
|
|
1429
|
+
success: { type: "boolean" },
|
|
1430
|
+
error: { type: "string" }
|
|
1431
|
+
}
|
|
1432
|
+
},
|
|
1433
|
+
500: {
|
|
1434
|
+
type: "object",
|
|
1435
|
+
properties: {
|
|
1436
|
+
success: { type: "boolean" },
|
|
1437
|
+
error: { type: "string" }
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
};
|
|
1442
|
+
var getConfigSchema = {
|
|
1443
|
+
description: "Get current gateway configuration",
|
|
1444
|
+
tags: ["Configuration"],
|
|
1445
|
+
summary: "Get Configuration",
|
|
1446
|
+
response: {
|
|
1447
|
+
200: {
|
|
1448
|
+
type: "object",
|
|
1449
|
+
properties: {
|
|
1450
|
+
success: { type: "boolean" },
|
|
1451
|
+
data: { type: "object" }
|
|
1452
|
+
}
|
|
1453
|
+
},
|
|
1454
|
+
500: {
|
|
1455
|
+
type: "object",
|
|
1456
|
+
properties: {
|
|
1457
|
+
success: { type: "boolean" },
|
|
1458
|
+
error: { type: "string" }
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
};
|
|
1098
1463
|
|
|
1099
1464
|
// src/routes/index.ts
|
|
1100
1465
|
var registerLatticeRoutes = (app2) => {
|
|
@@ -1159,6 +1524,18 @@ var registerLatticeRoutes = (app2) => {
|
|
|
1159
1524
|
"/api/assistants/:assistantId/threads/:threadId",
|
|
1160
1525
|
deleteThread
|
|
1161
1526
|
);
|
|
1527
|
+
app2.get(
|
|
1528
|
+
"/api/config",
|
|
1529
|
+
{ schema: getConfigSchema },
|
|
1530
|
+
getConfig
|
|
1531
|
+
);
|
|
1532
|
+
app2.put(
|
|
1533
|
+
"/api/config",
|
|
1534
|
+
{ schema: updateConfigSchema },
|
|
1535
|
+
updateConfig
|
|
1536
|
+
);
|
|
1537
|
+
app2.get("/api/models", getModels);
|
|
1538
|
+
app2.put("/api/models", updateModels);
|
|
1162
1539
|
};
|
|
1163
1540
|
|
|
1164
1541
|
// src/logger/Logger.ts
|
|
@@ -1361,54 +1738,6 @@ var configureSwagger = async (app2, customSwaggerConfig, customSwaggerUiConfig)
|
|
|
1361
1738
|
await app2.register(swaggerUi, swaggerUiConfig);
|
|
1362
1739
|
};
|
|
1363
1740
|
|
|
1364
|
-
// src/services/queue_service.ts
|
|
1365
|
-
import {
|
|
1366
|
-
queueLatticeManager,
|
|
1367
|
-
registerQueueLattice,
|
|
1368
|
-
getQueueLattice
|
|
1369
|
-
} from "@axiom-lattice/core";
|
|
1370
|
-
import { QueueType } from "@axiom-lattice/protocols";
|
|
1371
|
-
import { RedisQueueClient } from "@axiom-lattice/queue-redis";
|
|
1372
|
-
var DEFAULT_QUEUE_KEY = "default";
|
|
1373
|
-
var queueServiceType = process.env.QUEUE_SERVICE_TYPE || "memory";
|
|
1374
|
-
var setQueueServiceType = (type) => {
|
|
1375
|
-
queueServiceType = type;
|
|
1376
|
-
console.log(`Queue service type set to: ${type}`);
|
|
1377
|
-
const queueName = process.env.QUEUE_NAME || "tasks";
|
|
1378
|
-
const config = {
|
|
1379
|
-
name: "Default Queue Service",
|
|
1380
|
-
description: `Default ${type} queue service`,
|
|
1381
|
-
type: type === "redis" ? QueueType.REDIS : QueueType.MEMORY,
|
|
1382
|
-
queueName,
|
|
1383
|
-
options: type === "redis" ? {
|
|
1384
|
-
redisUrl: process.env.REDIS_URL,
|
|
1385
|
-
redisPassword: process.env.REDIS_PASSWORD
|
|
1386
|
-
} : void 0
|
|
1387
|
-
};
|
|
1388
|
-
if (queueLatticeManager.hasLattice(DEFAULT_QUEUE_KEY)) {
|
|
1389
|
-
queueLatticeManager.removeLattice(DEFAULT_QUEUE_KEY);
|
|
1390
|
-
}
|
|
1391
|
-
let client;
|
|
1392
|
-
if (type === "redis") {
|
|
1393
|
-
client = new RedisQueueClient(queueName, {
|
|
1394
|
-
redisUrl: process.env.REDIS_URL,
|
|
1395
|
-
redisPassword: process.env.REDIS_PASSWORD
|
|
1396
|
-
});
|
|
1397
|
-
}
|
|
1398
|
-
registerQueueLattice(DEFAULT_QUEUE_KEY, config, client);
|
|
1399
|
-
};
|
|
1400
|
-
var getQueueService = () => {
|
|
1401
|
-
if (!queueLatticeManager.hasLattice(DEFAULT_QUEUE_KEY)) {
|
|
1402
|
-
setQueueServiceType(queueServiceType);
|
|
1403
|
-
}
|
|
1404
|
-
return getQueueLattice(DEFAULT_QUEUE_KEY);
|
|
1405
|
-
};
|
|
1406
|
-
var popAgentTaskFromQueue = async () => {
|
|
1407
|
-
const queue = getQueueService();
|
|
1408
|
-
const result = await queue.pop();
|
|
1409
|
-
return result;
|
|
1410
|
-
};
|
|
1411
|
-
|
|
1412
1741
|
// src/services/agent_task_consumer.ts
|
|
1413
1742
|
import { eventBus, AGENT_TASK_EVENT } from "@axiom-lattice/core";
|
|
1414
1743
|
var handleAgentTask = async (taskRequest, retryCount = 0) => {
|