@solidxai/core 0.1.11-beta.2 → 0.1.11-beta.5
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/commands/migrate-removed-fields.command.d.ts +19 -0
- package/dist/commands/migrate-removed-fields.command.d.ts.map +1 -0
- package/dist/commands/migrate-removed-fields.command.js +77 -0
- package/dist/commands/migrate-removed-fields.command.js.map +1 -0
- package/dist/controllers/datasource-introspection.controller.d.ts +38 -1
- package/dist/controllers/datasource-introspection.controller.d.ts.map +1 -1
- package/dist/controllers/datasource-introspection.controller.js +38 -0
- package/dist/controllers/datasource-introspection.controller.js.map +1 -1
- package/dist/dtos/datasource-introspection-mapping.dto.d.ts +5 -0
- package/dist/dtos/datasource-introspection-mapping.dto.d.ts.map +1 -1
- package/dist/dtos/datasource-introspection-mapping.dto.js +24 -2
- package/dist/dtos/datasource-introspection-mapping.dto.js.map +1 -1
- package/dist/entities/{legacy-common-with-id.entity.d.ts → legacy-common-with-generated-id.entity.d.ts} +2 -2
- package/dist/entities/legacy-common-with-generated-id.entity.d.ts.map +1 -0
- package/dist/entities/{legacy-common-with-id.entity.js → legacy-common-with-generated-id.entity.js} +4 -4
- package/dist/entities/legacy-common-with-generated-id.entity.js.map +1 -0
- package/dist/entities/{legacy-common.entity.d.ts → legacy-common.entity-with-existing-id.entity.d.ts} +1 -1
- package/dist/entities/legacy-common.entity-with-existing-id.entity.d.ts.map +1 -0
- package/dist/entities/{legacy-common.entity.js → legacy-common.entity-with-existing-id.entity.js} +1 -1
- package/dist/entities/legacy-common.entity-with-existing-id.entity.js.map +1 -0
- package/dist/helpers/command.service.d.ts +2 -0
- package/dist/helpers/command.service.d.ts.map +1 -1
- package/dist/helpers/command.service.js +6 -2
- package/dist/helpers/command.service.js.map +1 -1
- package/dist/helpers/model-metadata-helper.service.js +3 -3
- package/dist/helpers/model-metadata-helper.service.js.map +1 -1
- package/dist/helpers/nodemon-heartbeat.d.ts +5 -0
- package/dist/helpers/nodemon-heartbeat.d.ts.map +1 -0
- package/dist/helpers/nodemon-heartbeat.js +67 -0
- package/dist/helpers/nodemon-heartbeat.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/interfaces/datasource-introspection-provider.interface.d.ts +1 -0
- package/dist/interfaces/datasource-introspection-provider.interface.d.ts.map +1 -1
- package/dist/interfaces/datasource-introspection-provider.interface.js.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.d.ts +7 -1
- package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.js +101 -1
- package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +121 -34
- package/dist/services/api-key.service.d.ts.map +1 -1
- package/dist/services/api-key.service.js.map +1 -1
- package/dist/services/computed-fields/entity/concat-entity-computed-field-provider.service.d.ts +4 -0
- package/dist/services/computed-fields/entity/concat-entity-computed-field-provider.service.d.ts.map +1 -1
- package/dist/services/computed-fields/entity/concat-entity-computed-field-provider.service.js +18 -1
- package/dist/services/computed-fields/entity/concat-entity-computed-field-provider.service.js.map +1 -1
- package/dist/services/crud.service.d.ts +1 -0
- package/dist/services/crud.service.d.ts.map +1 -1
- package/dist/services/crud.service.js +27 -0
- package/dist/services/crud.service.js.map +1 -1
- package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.d.ts.map +1 -1
- package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.js +12 -0
- package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.js.map +1 -1
- package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.d.ts.map +1 -1
- package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.js +2 -0
- package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.js.map +1 -1
- package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.d.ts.map +1 -1
- package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.js +28 -15
- package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.js.map +1 -1
- package/dist/services/datasource-introspection.service.d.ts +53 -2
- package/dist/services/datasource-introspection.service.d.ts.map +1 -1
- package/dist/services/datasource-introspection.service.js +559 -174
- package/dist/services/datasource-introspection.service.js.map +1 -1
- package/dist/services/import-transaction.service.d.ts.map +1 -1
- package/dist/services/import-transaction.service.js +3 -0
- package/dist/services/import-transaction.service.js.map +1 -1
- package/dist/services/mediaStorageProviders/file-s3-storage-provider.js +10 -10
- package/dist/services/mediaStorageProviders/file-s3-storage-provider.js.map +1 -1
- package/dist/services/model-metadata.service.d.ts +1 -0
- package/dist/services/model-metadata.service.d.ts.map +1 -1
- package/dist/services/model-metadata.service.js +28 -0
- package/dist/services/model-metadata.service.js.map +1 -1
- package/dist/services/module-metadata.service.d.ts.map +1 -1
- package/dist/services/module-metadata.service.js +27 -0
- package/dist/services/module-metadata.service.js.map +1 -1
- package/dist/services/removed-field-migration.service.d.ts +30 -0
- package/dist/services/removed-field-migration.service.d.ts.map +1 -0
- package/dist/services/removed-field-migration.service.js +319 -0
- package/dist/services/removed-field-migration.service.js.map +1 -0
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +4 -0
- package/dist/solid-core.module.js.map +1 -1
- package/dist/subscribers/audit.subscriber.d.ts +4 -1
- package/dist/subscribers/audit.subscriber.d.ts.map +1 -1
- package/dist/subscribers/audit.subscriber.js +54 -6
- package/dist/subscribers/audit.subscriber.js.map +1 -1
- package/dist-tests/api/authenticate.spec.js +119 -0
- package/dist-tests/api/authenticate.spec.js.map +1 -0
- package/dist-tests/api/crud-service.findOne.cityMaster.spec.js +97 -0
- package/dist-tests/api/crud-service.findOne.cityMaster.spec.js.map +1 -0
- package/dist-tests/api/ping.spec.js +21 -0
- package/dist-tests/api/ping.spec.js.map +1 -0
- package/dist-tests/helpers/auth.js +41 -0
- package/dist-tests/helpers/auth.js.map +1 -0
- package/dist-tests/helpers/env.js +11 -0
- package/dist-tests/helpers/env.js.map +1 -0
- package/docs/agent-hub-grooming.md +301 -0
- package/docs/dashboards/AGENTIC_DASHBOARD_IMPLEMENTATION_PLAN.md +438 -0
- package/docs/dashboards/dashboard-curl-smoke-tests.txt +146 -0
- package/docs/dashboards/delete-legacy-dashboard-metadata.sql +172 -0
- package/docs/datasource-introspection-ddl-analysis.md +326 -0
- package/docs/datasource-introspection-implementation-plan.md +306 -0
- package/docs/grouping-enhancements.md +89 -0
- package/docs/java-spring/README.md +3 -0
- package/docs/java-spring/solid-core-module-deep-dive-report.md +1317 -0
- package/docs/module-package-import-handoff.md +691 -0
- package/docs/seed-changes.md +65 -0
- package/docs/test-data-workflow.md +200 -0
- package/docs/type-declaration-import-issue.md +24 -0
- package/package.json +1 -1
- package/src/commands/migrate-removed-fields.command.ts +81 -0
- package/src/controllers/datasource-introspection.controller.ts +27 -1
- package/src/dtos/datasource-introspection-mapping.dto.ts +15 -0
- package/src/entities/{legacy-common-with-id.entity.ts → legacy-common-with-generated-id.entity.ts} +1 -1
- package/src/helpers/command.service.ts +10 -2
- package/src/helpers/model-metadata-helper.service.ts +1 -1
- package/src/helpers/nodemon-heartbeat.ts +59 -0
- package/src/index.ts +3 -2
- package/src/interfaces/datasource-introspection-provider.interface.ts +1 -0
- package/src/seeders/module-metadata-seeder.service.ts +118 -0
- package/src/seeders/seed-data/solid-core-metadata.json +121 -34
- package/src/services/api-key.service.ts +36 -36
- package/src/services/computed-fields/entity/concat-entity-computed-field-provider.service.ts +15 -1
- package/src/services/crud.service.ts +36 -11
- package/src/services/datasource-introspection/mssql-datasource-introspection-provider.service.ts +12 -0
- package/src/services/datasource-introspection/mysql-datasource-introspection-provider.service.ts +2 -0
- package/src/services/datasource-introspection/postgres-datasource-introspection-provider.service.ts +28 -15
- package/src/services/datasource-introspection.service.ts +653 -150
- package/src/services/import-transaction.service.ts +5 -0
- package/src/services/mediaStorageProviders/file-s3-storage-provider.ts +2 -2
- package/src/services/model-metadata.service.ts +45 -1
- package/src/services/module-metadata.service.ts +35 -0
- package/src/services/removed-field-migration.service.ts +334 -0
- package/src/solid-core.module.ts +4 -0
- package/src/subscribers/audit.subscriber.ts +70 -5
- package/.claude/settings.local.json +0 -15
- package/CLAUDE.md +0 -26
- package/dist/entities/legacy-common-with-id.entity.d.ts.map +0 -1
- package/dist/entities/legacy-common-with-id.entity.js.map +0 -1
- package/dist/entities/legacy-common.entity.d.ts.map +0 -1
- package/dist/entities/legacy-common.entity.js.map +0 -1
- package/src/services/1.js +0 -6
- /package/src/entities/{legacy-common.entity.ts → legacy-common.entity-with-existing-id.entity.ts} +0 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const test_1 = require("@playwright/test");
|
|
4
|
+
const env_1 = require("../helpers/env");
|
|
5
|
+
const baseURL = process.env.API_BASE_URL ?? "http://localhost:3000";
|
|
6
|
+
const TEST_USER_EMAIL = (0, env_1.getRequiredEnv)("TEST_USER_EMAIL");
|
|
7
|
+
const TEST_USER_PASSWORD = (0, env_1.getRequiredEnv)("TEST_USER_PASSWORD");
|
|
8
|
+
function base64UrlDecode(input) {
|
|
9
|
+
const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
10
|
+
const padded = normalized.length % 4 === 0
|
|
11
|
+
? normalized
|
|
12
|
+
: normalized.padEnd(normalized.length + (4 - (normalized.length % 4)), "=");
|
|
13
|
+
return Buffer.from(padded, "base64").toString("utf-8");
|
|
14
|
+
}
|
|
15
|
+
function validateJwt(token) {
|
|
16
|
+
const parts = token.split(".");
|
|
17
|
+
if (parts.length !== 3) {
|
|
18
|
+
throw new Error("JWT must have three dot-separated parts.");
|
|
19
|
+
}
|
|
20
|
+
const headerJson = JSON.parse(base64UrlDecode(parts[0]));
|
|
21
|
+
const payloadJson = JSON.parse(base64UrlDecode(parts[1]));
|
|
22
|
+
if (!headerJson || typeof headerJson !== "object") {
|
|
23
|
+
throw new Error("JWT header must be a JSON object.");
|
|
24
|
+
}
|
|
25
|
+
if (!payloadJson || typeof payloadJson !== "object") {
|
|
26
|
+
throw new Error("JWT payload must be a JSON object.");
|
|
27
|
+
}
|
|
28
|
+
if (typeof payloadJson.exp !== "number") {
|
|
29
|
+
throw new Error("JWT payload.exp must be a number.");
|
|
30
|
+
}
|
|
31
|
+
return payloadJson;
|
|
32
|
+
}
|
|
33
|
+
(0, test_1.test)("API: authenticate succeeds with valid credentials", async () => {
|
|
34
|
+
const api = await test_1.request.newContext({
|
|
35
|
+
baseURL,
|
|
36
|
+
extraHTTPHeaders: {
|
|
37
|
+
accept: "*/*",
|
|
38
|
+
"content-type": "application/json",
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
try {
|
|
42
|
+
const res = await api.post("/api/iam/authenticate", {
|
|
43
|
+
data: {
|
|
44
|
+
email: TEST_USER_EMAIL,
|
|
45
|
+
username: "",
|
|
46
|
+
password: TEST_USER_PASSWORD,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
(0, test_1.expect)(res.status()).toBe(200);
|
|
50
|
+
const json = await res.json();
|
|
51
|
+
(0, test_1.expect)(json.statusCode).toBe(200);
|
|
52
|
+
(0, test_1.expect)(Array.isArray(json.message)).toBe(true);
|
|
53
|
+
(0, test_1.expect)(json.message.length).toBe(0);
|
|
54
|
+
(0, test_1.expect)(json.error).toBe("");
|
|
55
|
+
const user = json.data?.user;
|
|
56
|
+
(0, test_1.expect)(user, "Expected data.user to be an object.").toBeTruthy();
|
|
57
|
+
(0, test_1.expect)(typeof user).toBe("object");
|
|
58
|
+
const email = user?.email;
|
|
59
|
+
(0, test_1.expect)(typeof email).toBe("string");
|
|
60
|
+
if (email === TEST_USER_EMAIL) {
|
|
61
|
+
(0, test_1.expect)(email).toBe(TEST_USER_EMAIL);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
(0, test_1.expect)(email.length).toBeGreaterThan(0);
|
|
65
|
+
}
|
|
66
|
+
(0, test_1.expect)(typeof user?.mobile).toBe("string");
|
|
67
|
+
(0, test_1.expect)(typeof user?.username).toBe("string");
|
|
68
|
+
(0, test_1.expect)(typeof user?.forcePasswordChange).toBe("boolean");
|
|
69
|
+
(0, test_1.expect)(typeof user?.id).toBe("number");
|
|
70
|
+
const roles = user?.roles;
|
|
71
|
+
(0, test_1.expect)(Array.isArray(roles)).toBe(true);
|
|
72
|
+
if (Array.isArray(roles)) {
|
|
73
|
+
(0, test_1.expect)(roles.every((role) => typeof role === "string")).toBe(true);
|
|
74
|
+
(0, test_1.expect)(roles).toContain("Admin");
|
|
75
|
+
}
|
|
76
|
+
const accessToken = json.data?.accessToken;
|
|
77
|
+
const refreshToken = json.data?.refreshToken;
|
|
78
|
+
(0, test_1.expect)(typeof accessToken).toBe("string");
|
|
79
|
+
(0, test_1.expect)(typeof refreshToken).toBe("string");
|
|
80
|
+
const accessPayload = validateJwt(accessToken);
|
|
81
|
+
const refreshPayload = validateJwt(refreshToken);
|
|
82
|
+
(0, test_1.expect)(typeof accessPayload.exp).toBe("number");
|
|
83
|
+
(0, test_1.expect)(typeof refreshPayload.exp).toBe("number");
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
await api.dispose();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
(0, test_1.test)("API: authenticate fails with wrong password", async () => {
|
|
90
|
+
const api = await test_1.request.newContext({
|
|
91
|
+
baseURL,
|
|
92
|
+
extraHTTPHeaders: {
|
|
93
|
+
accept: "*/*",
|
|
94
|
+
"content-type": "application/json",
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
try {
|
|
98
|
+
const res = await api.post("/api/iam/authenticate", {
|
|
99
|
+
data: {
|
|
100
|
+
email: TEST_USER_EMAIL,
|
|
101
|
+
username: "",
|
|
102
|
+
password: `${TEST_USER_PASSWORD}__wrong`,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
(0, test_1.expect)(res.status()).toBe(401);
|
|
106
|
+
const json = await res.json();
|
|
107
|
+
(0, test_1.expect)(json.statusCode).toBe(401);
|
|
108
|
+
(0, test_1.expect)(json.statusCodeMessage).toBe("Unauthorized");
|
|
109
|
+
(0, test_1.expect)(json.message).toBe("Invalid credentials");
|
|
110
|
+
(0, test_1.expect)(json.error).toBe("Invalid credentials");
|
|
111
|
+
(0, test_1.expect)(json.data?.statusCode).toBe(401);
|
|
112
|
+
(0, test_1.expect)(json.data?.error).toBe("Unauthorized");
|
|
113
|
+
(0, test_1.expect)(json.data?.message).toBe("Invalid credentials");
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
await api.dispose();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
//# sourceMappingURL=authenticate.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authenticate.spec.js","sourceRoot":"","sources":["../../tests/api/authenticate.spec.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, request, test } from \"@playwright/test\";\nimport { getRequiredEnv } from \"../helpers/env\";\n\ntype JwtPayload = {\n exp?: number;\n [key: string]: unknown;\n};\n\nconst baseURL = process.env.API_BASE_URL ?? \"http://localhost:3000\";\nconst TEST_USER_EMAIL = getRequiredEnv(\"TEST_USER_EMAIL\");\nconst TEST_USER_PASSWORD = getRequiredEnv(\"TEST_USER_PASSWORD\");\n\nfunction base64UrlDecode(input: string): string {\n const normalized = input.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padded =\n normalized.length % 4 === 0\n ? normalized\n : normalized.padEnd(\n normalized.length + (4 - (normalized.length % 4)),\n \"=\"\n );\n return Buffer.from(padded, \"base64\").toString(\"utf-8\");\n}\n\nfunction validateJwt(token: string): JwtPayload {\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n throw new Error(\"JWT must have three dot-separated parts.\");\n }\n\n const headerJson = JSON.parse(base64UrlDecode(parts[0]));\n const payloadJson = JSON.parse(base64UrlDecode(parts[1]));\n\n if (!headerJson || typeof headerJson !== \"object\") {\n throw new Error(\"JWT header must be a JSON object.\");\n }\n\n if (!payloadJson || typeof payloadJson !== \"object\") {\n throw new Error(\"JWT payload must be a JSON object.\");\n }\n\n if (typeof payloadJson.exp !== \"number\") {\n throw new Error(\"JWT payload.exp must be a number.\");\n }\n\n return payloadJson as JwtPayload;\n}\n\ntest(\"API: authenticate succeeds with valid credentials\", async () => {\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: {\n accept: \"*/*\",\n \"content-type\": \"application/json\",\n },\n });\n\n try {\n const res = await api.post(\"/api/iam/authenticate\", {\n data: {\n email: TEST_USER_EMAIL,\n username: \"\",\n password: TEST_USER_PASSWORD,\n },\n });\n\n expect(res.status()).toBe(200);\n const json = await res.json();\n\n expect(json.statusCode).toBe(200);\n expect(Array.isArray(json.message)).toBe(true);\n expect(json.message.length).toBe(0);\n expect(json.error).toBe(\"\");\n\n const user = json.data?.user as Record<string, unknown> | undefined;\n expect(user, \"Expected data.user to be an object.\").toBeTruthy();\n expect(typeof user).toBe(\"object\");\n\n const email = user?.email;\n expect(typeof email).toBe(\"string\");\n if (email === TEST_USER_EMAIL) {\n expect(email).toBe(TEST_USER_EMAIL);\n } else {\n // If your API returns a fixed system email, replace this with an exact match.\n expect((email as string).length).toBeGreaterThan(0);\n }\n\n expect(typeof user?.mobile).toBe(\"string\");\n expect(typeof user?.username).toBe(\"string\");\n expect(typeof user?.forcePasswordChange).toBe(\"boolean\");\n expect(typeof user?.id).toBe(\"number\");\n\n const roles = user?.roles;\n expect(Array.isArray(roles)).toBe(true);\n if (Array.isArray(roles)) {\n expect(roles.every((role) => typeof role === \"string\")).toBe(true);\n expect(roles).toContain(\"Admin\");\n }\n\n const accessToken = json.data?.accessToken;\n const refreshToken = json.data?.refreshToken;\n expect(typeof accessToken).toBe(\"string\");\n expect(typeof refreshToken).toBe(\"string\");\n\n const accessPayload = validateJwt(accessToken as string);\n const refreshPayload = validateJwt(refreshToken as string);\n\n expect(typeof accessPayload.exp).toBe(\"number\");\n expect(typeof refreshPayload.exp).toBe(\"number\");\n } finally {\n await api.dispose();\n }\n});\n\ntest(\"API: authenticate fails with wrong password\", async () => {\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: {\n accept: \"*/*\",\n \"content-type\": \"application/json\",\n },\n });\n\n try {\n const res = await api.post(\"/api/iam/authenticate\", {\n data: {\n email: TEST_USER_EMAIL,\n username: \"\",\n password: `${TEST_USER_PASSWORD}__wrong`,\n },\n });\n\n expect(res.status()).toBe(401);\n const json = await res.json();\n\n expect(json.statusCode).toBe(401);\n expect(json.statusCodeMessage).toBe(\"Unauthorized\");\n expect(json.message).toBe(\"Invalid credentials\");\n expect(json.error).toBe(\"Invalid credentials\");\n expect(json.data?.statusCode).toBe(401);\n expect(json.data?.error).toBe(\"Unauthorized\");\n expect(json.data?.message).toBe(\"Invalid credentials\");\n } finally {\n await api.dispose();\n }\n});\n"]}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const test_1 = require("@playwright/test");
|
|
4
|
+
const auth_1 = require("../helpers/auth");
|
|
5
|
+
const baseURL = process.env.API_BASE_URL ?? "http://localhost:3000";
|
|
6
|
+
async function getCityByName(name) {
|
|
7
|
+
const api = await test_1.request.newContext({
|
|
8
|
+
baseURL,
|
|
9
|
+
extraHTTPHeaders: await (0, auth_1.getAuthHeaders)(baseURL),
|
|
10
|
+
});
|
|
11
|
+
try {
|
|
12
|
+
const res = await api.get(`/api/city-master?filters[name][$eq]=${encodeURIComponent(name)}&limit=1&offset=0`);
|
|
13
|
+
(0, test_1.expect)(res.status()).toBe(200);
|
|
14
|
+
const json = await res.json();
|
|
15
|
+
const record = json?.data?.records?.[0];
|
|
16
|
+
(0, test_1.expect)(record, `Expected city '${name}' in testData`).toBeTruthy();
|
|
17
|
+
(0, test_1.expect)(record.id, "Expected record to have id").toBeTruthy();
|
|
18
|
+
return record;
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
await api.dispose();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
test_1.test.describe("CRUDService.findOne (cityMaster)", () => {
|
|
25
|
+
(0, test_1.test)("returns a city by id", async () => {
|
|
26
|
+
const city = await getCityByName("Mumbai");
|
|
27
|
+
const api = await test_1.request.newContext({
|
|
28
|
+
baseURL,
|
|
29
|
+
extraHTTPHeaders: await (0, auth_1.getAuthHeaders)(baseURL),
|
|
30
|
+
});
|
|
31
|
+
try {
|
|
32
|
+
const res = await api.get(`/api/city-master/${city.id}`);
|
|
33
|
+
(0, test_1.expect)(res.status()).toBe(200);
|
|
34
|
+
const json = await res.json();
|
|
35
|
+
(0, test_1.expect)(json?.data?.id).toBe(city.id);
|
|
36
|
+
(0, test_1.expect)(json?.data?.name).toBe("Mumbai");
|
|
37
|
+
(0, test_1.expect)(json?.data?.description).toBe("Mumbai city");
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
await api.dispose();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
(0, test_1.test)("supports fields[] selection", async () => {
|
|
44
|
+
const city = await getCityByName("Pune");
|
|
45
|
+
const api = await test_1.request.newContext({
|
|
46
|
+
baseURL,
|
|
47
|
+
extraHTTPHeaders: await (0, auth_1.getAuthHeaders)(baseURL),
|
|
48
|
+
});
|
|
49
|
+
try {
|
|
50
|
+
const res = await api.get(`/api/city-master/${city.id}?fields[]=id&fields[]=name`);
|
|
51
|
+
(0, test_1.expect)(res.status()).toBe(200);
|
|
52
|
+
const json = await res.json();
|
|
53
|
+
(0, test_1.expect)(json?.data?.id).toBe(city.id);
|
|
54
|
+
(0, test_1.expect)(json?.data?.name).toBe("Pune");
|
|
55
|
+
(0, test_1.expect)(json?.data?.description).toBeUndefined();
|
|
56
|
+
(0, test_1.expect)(json?.data?.state).toBeUndefined();
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
await api.dispose();
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
(0, test_1.test)("supports populate=state", async () => {
|
|
63
|
+
const city = await getCityByName("Bengaluru");
|
|
64
|
+
const api = await test_1.request.newContext({
|
|
65
|
+
baseURL,
|
|
66
|
+
extraHTTPHeaders: await (0, auth_1.getAuthHeaders)(baseURL),
|
|
67
|
+
});
|
|
68
|
+
try {
|
|
69
|
+
const res = await api.get(`/api/city-master/${city.id}?populate=state`);
|
|
70
|
+
(0, test_1.expect)(res.status()).toBe(200);
|
|
71
|
+
const json = await res.json();
|
|
72
|
+
(0, test_1.expect)(json?.data?.name).toBe("Bengaluru");
|
|
73
|
+
(0, test_1.expect)(json?.data?.state).toBeTruthy();
|
|
74
|
+
(0, test_1.expect)(json?.data?.state?.name).toBe("Karnataka");
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
await api.dispose();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
(0, test_1.test)("returns 404 for missing id", async () => {
|
|
81
|
+
const api = await test_1.request.newContext({
|
|
82
|
+
baseURL,
|
|
83
|
+
extraHTTPHeaders: await (0, auth_1.getAuthHeaders)(baseURL),
|
|
84
|
+
});
|
|
85
|
+
try {
|
|
86
|
+
const res = await api.get(`/api/city-master/99999999`);
|
|
87
|
+
(0, test_1.expect)(res.status()).toBe(404);
|
|
88
|
+
const json = await res.json();
|
|
89
|
+
(0, test_1.expect)(String(json?.message)).toContain("cityMaster");
|
|
90
|
+
(0, test_1.expect)(String(json?.message)).toContain("not found");
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
await api.dispose();
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
//# sourceMappingURL=crud-service.findOne.cityMaster.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crud-service.findOne.cityMaster.spec.js","sourceRoot":"","sources":["../../tests/api/crud-service.findOne.cityMaster.spec.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, request, test } from \"@playwright/test\";\nimport { getAuthHeaders } from \"../helpers/auth\";\n\nconst baseURL = process.env.API_BASE_URL ?? \"http://localhost:3000\";\n\nasync function getCityByName(name: string) {\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: await getAuthHeaders(baseURL),\n });\n try {\n const res = await api.get(\n `/api/city-master?filters[name][$eq]=${encodeURIComponent(name)}&limit=1&offset=0`\n );\n expect(res.status()).toBe(200);\n const json = await res.json();\n\n const record = json?.data?.records?.[0];\n expect(record, `Expected city '${name}' in testData`).toBeTruthy();\n expect(record.id, \"Expected record to have id\").toBeTruthy();\n\n return record;\n } finally {\n await api.dispose();\n }\n}\n\ntest.describe(\"CRUDService.findOne (cityMaster)\", () => {\n test(\"returns a city by id\", async () => {\n const city = await getCityByName(\"Mumbai\");\n\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: await getAuthHeaders(baseURL),\n });\n try {\n const res = await api.get(`/api/city-master/${city.id}`);\n expect(res.status()).toBe(200);\n\n const json = await res.json();\n expect(json?.data?.id).toBe(city.id);\n expect(json?.data?.name).toBe(\"Mumbai\");\n expect(json?.data?.description).toBe(\"Mumbai city\");\n } finally {\n await api.dispose();\n }\n });\n\n test(\"supports fields[] selection\", async () => {\n const city = await getCityByName(\"Pune\");\n\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: await getAuthHeaders(baseURL),\n });\n try {\n const res = await api.get(\n `/api/city-master/${city.id}?fields[]=id&fields[]=name`\n );\n expect(res.status()).toBe(200);\n\n const json = await res.json();\n expect(json?.data?.id).toBe(city.id);\n expect(json?.data?.name).toBe(\"Pune\");\n expect(json?.data?.description).toBeUndefined();\n expect(json?.data?.state).toBeUndefined();\n } finally {\n await api.dispose();\n }\n });\n\n test(\"supports populate=state\", async () => {\n const city = await getCityByName(\"Bengaluru\");\n\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: await getAuthHeaders(baseURL),\n });\n try {\n const res = await api.get(`/api/city-master/${city.id}?populate=state`);\n expect(res.status()).toBe(200);\n\n const json = await res.json();\n expect(json?.data?.name).toBe(\"Bengaluru\");\n expect(json?.data?.state).toBeTruthy();\n expect(json?.data?.state?.name).toBe(\"Karnataka\");\n } finally {\n await api.dispose();\n }\n });\n\n test(\"returns 404 for missing id\", async () => {\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: await getAuthHeaders(baseURL),\n });\n try {\n const res = await api.get(`/api/city-master/99999999`);\n expect(res.status()).toBe(404);\n\n const json = await res.json();\n expect(String(json?.message)).toContain(\"cityMaster\");\n expect(String(json?.message)).toContain(\"not found\");\n } finally {\n await api.dispose();\n }\n });\n});\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const test_1 = require("@playwright/test");
|
|
4
|
+
(0, test_1.test)("GET /api/ping returns pong", async () => {
|
|
5
|
+
const baseURL = process.env.BASE_URL || "http://localhost:3000";
|
|
6
|
+
if (!baseURL) {
|
|
7
|
+
throw new Error("baseURL is not configured. Set API_BASE_URL or use the default.");
|
|
8
|
+
}
|
|
9
|
+
const api = await test_1.request.newContext({ baseURL });
|
|
10
|
+
const res = await api.get("/api/ping");
|
|
11
|
+
(0, test_1.expect)(res.status()).toBe(200);
|
|
12
|
+
const body = await res.json();
|
|
13
|
+
(0, test_1.expect)(body).toEqual({
|
|
14
|
+
statusCode: 200,
|
|
15
|
+
message: [],
|
|
16
|
+
error: "",
|
|
17
|
+
data: { pong: "v1.0.2" },
|
|
18
|
+
});
|
|
19
|
+
await api.dispose();
|
|
20
|
+
});
|
|
21
|
+
//# sourceMappingURL=ping.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ping.spec.js","sourceRoot":"","sources":["../../tests/api/ping.spec.ts"],"names":[],"mappings":";;AAAA,2CAAyD;AAEzD,IAAA,WAAI,EAAC,4BAA4B,EAAE,KAAK,IAAI,EAAE;IAC5C,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,uBAAuB,CAAC;IAChE,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;IACrF,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,cAAO,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;IAElD,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAEvC,IAAA,aAAM,EAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAE/B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAA,aAAM,EAAC,IAAI,CAAC,CAAC,OAAO,CAAC;QACnB,UAAU,EAAE,GAAG;QACf,OAAO,EAAE,EAAE;QACX,KAAK,EAAE,EAAE;QACT,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;KACzB,CAAC,CAAC;IAEH,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC;AACtB,CAAC,CAAC,CAAC","sourcesContent":["import { expect, request, test } from \"@playwright/test\";\n\ntest(\"GET /api/ping returns pong\", async () => {\n const baseURL = process.env.BASE_URL || \"http://localhost:3000\";\n if (!baseURL) {\n throw new Error(\"baseURL is not configured. Set API_BASE_URL or use the default.\");\n }\n const api = await request.newContext({ baseURL });\n\n const res = await api.get(\"/api/ping\");\n\n expect(res.status()).toBe(200);\n\n const body = await res.json();\n expect(body).toEqual({\n statusCode: 200,\n message: [],\n error: \"\",\n data: { pong: \"v1.0.2\" },\n });\n\n await api.dispose();\n});\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getAccessToken = getAccessToken;
|
|
4
|
+
exports.getAuthHeaders = getAuthHeaders;
|
|
5
|
+
const test_1 = require("@playwright/test");
|
|
6
|
+
const env_1 = require("./env");
|
|
7
|
+
async function getAccessToken(baseURL) {
|
|
8
|
+
const TEST_USER_EMAIL = (0, env_1.getRequiredEnv)("TEST_USER_EMAIL");
|
|
9
|
+
const TEST_USER_PASSWORD = (0, env_1.getRequiredEnv)("TEST_USER_PASSWORD");
|
|
10
|
+
const api = await test_1.request.newContext({
|
|
11
|
+
baseURL,
|
|
12
|
+
extraHTTPHeaders: {
|
|
13
|
+
accept: "*/*",
|
|
14
|
+
"content-type": "application/json",
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
try {
|
|
18
|
+
const res = await api.post("/api/iam/authenticate", {
|
|
19
|
+
data: {
|
|
20
|
+
email: TEST_USER_EMAIL,
|
|
21
|
+
username: "",
|
|
22
|
+
password: TEST_USER_PASSWORD,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
(0, test_1.expect)(res.status()).toBe(200);
|
|
26
|
+
const json = await res.json();
|
|
27
|
+
const token = json?.data?.accessToken;
|
|
28
|
+
(0, test_1.expect)(token, "Expected access token from authenticate endpoint.").toBeTruthy();
|
|
29
|
+
return token;
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
await api.dispose();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function getAuthHeaders(baseURL) {
|
|
36
|
+
const token = await getAccessToken(baseURL);
|
|
37
|
+
return {
|
|
38
|
+
authorization: `Bearer ${token}`,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../tests/helpers/auth.ts"],"names":[],"mappings":";;AAGA,wCA6BC;AAED,wCAKC;AAvCD,2CAAmD;AACnD,+BAAuC;AAEhC,KAAK,UAAU,cAAc,CAAC,OAAe;IAClD,MAAM,eAAe,GAAG,IAAA,oBAAc,EAAC,iBAAiB,CAAC,CAAC;IAC1D,MAAM,kBAAkB,GAAG,IAAA,oBAAc,EAAC,oBAAoB,CAAC,CAAC;IAEhE,MAAM,GAAG,GAAG,MAAM,cAAO,CAAC,UAAU,CAAC;QACnC,OAAO;QACP,gBAAgB,EAAE;YAChB,MAAM,EAAE,KAAK;YACb,cAAc,EAAE,kBAAkB;SACnC;KACF,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,uBAAuB,EAAE;YAClD,IAAI,EAAE;gBACJ,KAAK,EAAE,eAAe;gBACtB,QAAQ,EAAE,EAAE;gBACZ,QAAQ,EAAE,kBAAkB;aAC7B;SACF,CAAC,CAAC;QAEH,IAAA,aAAM,EAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAG,IAAI,EAAE,IAAI,EAAE,WAAiC,CAAC;QAC5D,IAAA,aAAM,EAAC,KAAK,EAAE,mDAAmD,CAAC,CAAC,UAAU,EAAE,CAAC;QAChF,OAAO,KAAe,CAAC;IACzB,CAAC;YAAS,CAAC;QACT,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC;IACtB,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,cAAc,CAAC,OAAe;IAClD,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,CAAC;IAC5C,OAAO;QACL,aAAa,EAAE,UAAU,KAAK,EAAE;KACjC,CAAC;AACJ,CAAC","sourcesContent":["import { expect, request } from \"@playwright/test\";\nimport { getRequiredEnv } from \"./env\";\n\nexport async function getAccessToken(baseURL: string) {\n const TEST_USER_EMAIL = getRequiredEnv(\"TEST_USER_EMAIL\");\n const TEST_USER_PASSWORD = getRequiredEnv(\"TEST_USER_PASSWORD\");\n\n const api = await request.newContext({\n baseURL,\n extraHTTPHeaders: {\n accept: \"*/*\",\n \"content-type\": \"application/json\",\n },\n });\n\n try {\n const res = await api.post(\"/api/iam/authenticate\", {\n data: {\n email: TEST_USER_EMAIL,\n username: \"\",\n password: TEST_USER_PASSWORD,\n },\n });\n\n expect(res.status()).toBe(200);\n const json = await res.json();\n const token = json?.data?.accessToken as string | undefined;\n expect(token, \"Expected access token from authenticate endpoint.\").toBeTruthy();\n return token as string;\n } finally {\n await api.dispose();\n }\n}\n\nexport async function getAuthHeaders(baseURL: string) {\n const token = await getAccessToken(baseURL);\n return {\n authorization: `Bearer ${token}`,\n };\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getRequiredEnv = getRequiredEnv;
|
|
4
|
+
function getRequiredEnv(name) {
|
|
5
|
+
const value = process.env[name];
|
|
6
|
+
if (!value) {
|
|
7
|
+
throw new Error(`Missing required env var: ${name}`);
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../tests/helpers/env.ts"],"names":[],"mappings":";;AAAA,wCAMC;AAND,SAAgB,cAAc,CAAC,IAAY;IACzC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["export function getRequiredEnv(name: string): string {\n const value = process.env[name];\n if (!value) {\n throw new Error(`Missing required env var: ${name}`);\n }\n return value;\n}\n"]}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# SolidX AI Agent Builder / Agent Registry Vision
|
|
2
|
+
|
|
3
|
+
## Concept
|
|
4
|
+
|
|
5
|
+
The **AI Agent Builder** is a SolidX module that allows teams to define, configure, test, and deploy AI agents as first-class metadata-driven entities.
|
|
6
|
+
|
|
7
|
+
The goal is to make agents configurable through SolidX rather than hardcoded in application code.
|
|
8
|
+
|
|
9
|
+
Each agent is powered by a **Mini-SWE style runtime loop**, but the behaviour of the agent is controlled by SolidX metadata:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
Agent Definition
|
|
13
|
+
+ Skills
|
|
14
|
+
+ Tools
|
|
15
|
+
+ Knowledge Base
|
|
16
|
+
+ Policies
|
|
17
|
+
+ Output Schema
|
|
18
|
+
= Runnable Enterprise Agent
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Module Installation
|
|
24
|
+
|
|
25
|
+
The AI Agent Builder can be installed as a standard SolidX module.
|
|
26
|
+
|
|
27
|
+
Once installed, it brings its own domain model, such as:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
Agent
|
|
31
|
+
Agent Skill
|
|
32
|
+
Agent Tool
|
|
33
|
+
Agent Knowledge Base
|
|
34
|
+
Agent Policy
|
|
35
|
+
Agent Run
|
|
36
|
+
Agent Interaction
|
|
37
|
+
Tool Call Log
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
However, the actual agent configuration can be saved directly into SolidX metadata JSON, instead of requiring every configuration detail to be stored as transactional database rows.
|
|
41
|
+
|
|
42
|
+
The database can mainly store runtime records such as:
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
Agent Run
|
|
46
|
+
Conversation History
|
|
47
|
+
Tool Calls
|
|
48
|
+
Execution Logs
|
|
49
|
+
Errors
|
|
50
|
+
Audit Trail
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Agent Configuration UI
|
|
56
|
+
|
|
57
|
+
SolidX can provide either:
|
|
58
|
+
|
|
59
|
+
1. A native SolidX CRUD-style UI, or
|
|
60
|
+
2. A custom Agent Builder UI
|
|
61
|
+
|
|
62
|
+
to manage agent artefacts.
|
|
63
|
+
|
|
64
|
+
The user can configure:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
Agent name
|
|
68
|
+
Agent description
|
|
69
|
+
System instructions
|
|
70
|
+
Allowed skills
|
|
71
|
+
Allowed tools
|
|
72
|
+
Linked knowledge bases
|
|
73
|
+
Runtime limits
|
|
74
|
+
Approval rules
|
|
75
|
+
Output schema
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Example:
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
Customer Support Agent
|
|
82
|
+
- Skill: Complaint Handling
|
|
83
|
+
- Skill: Refund Policy Interpretation
|
|
84
|
+
- Tool: Gmail
|
|
85
|
+
- Tool: CRM
|
|
86
|
+
- Knowledge Base: Support SOPs
|
|
87
|
+
- Runtime: Mini-SWE
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Skills
|
|
93
|
+
|
|
94
|
+
A skill represents reusable know-how.
|
|
95
|
+
|
|
96
|
+
Examples:
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
Contract Review
|
|
100
|
+
Invoice Validation
|
|
101
|
+
Customer Escalation Handling
|
|
102
|
+
Code Review
|
|
103
|
+
Document Classification
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
A skill may contain:
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
Instructions
|
|
110
|
+
Prompt templates
|
|
111
|
+
Examples
|
|
112
|
+
Decision criteria
|
|
113
|
+
Expected output format
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Skill = **how to think / how to perform a task**
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Tools
|
|
121
|
+
|
|
122
|
+
Tools are executable capabilities available to agents.
|
|
123
|
+
|
|
124
|
+
Examples:
|
|
125
|
+
|
|
126
|
+
```text
|
|
127
|
+
Send Email
|
|
128
|
+
Search Gmail
|
|
129
|
+
Query Database
|
|
130
|
+
Create Ticket
|
|
131
|
+
Read File
|
|
132
|
+
Call API
|
|
133
|
+
Search Knowledge Base
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Tool = **what action the agent can perform**
|
|
137
|
+
|
|
138
|
+
SolidX can provide a screen where all registered/discovered tools are visible.
|
|
139
|
+
|
|
140
|
+
The tool registry can include:
|
|
141
|
+
|
|
142
|
+
```text
|
|
143
|
+
Native SolidX tools
|
|
144
|
+
Internal APIs
|
|
145
|
+
MCP tools
|
|
146
|
+
Composio tools
|
|
147
|
+
Custom enterprise tools
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Each tool can have metadata:
|
|
151
|
+
|
|
152
|
+
```text
|
|
153
|
+
Name
|
|
154
|
+
Description
|
|
155
|
+
Input schema
|
|
156
|
+
Output schema
|
|
157
|
+
Auth mode
|
|
158
|
+
Approval requirement
|
|
159
|
+
Allowed roles
|
|
160
|
+
Provider
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Knowledge Base
|
|
166
|
+
|
|
167
|
+
Agents can be linked to one or more knowledge bases.
|
|
168
|
+
|
|
169
|
+
Examples:
|
|
170
|
+
|
|
171
|
+
```text
|
|
172
|
+
Company policies
|
|
173
|
+
Product documentation
|
|
174
|
+
Support articles
|
|
175
|
+
Technical specifications
|
|
176
|
+
SOPs
|
|
177
|
+
Contract templates
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
This allows each agent to operate with controlled enterprise context.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Agent Runtime
|
|
185
|
+
|
|
186
|
+
Each configured agent is executed as a Mini-SWE loop.
|
|
187
|
+
|
|
188
|
+
When invoked, SolidX spawns an agent run and gives it:
|
|
189
|
+
|
|
190
|
+
```text
|
|
191
|
+
Goal
|
|
192
|
+
User input
|
|
193
|
+
Agent metadata
|
|
194
|
+
Relevant skills
|
|
195
|
+
Available tools
|
|
196
|
+
Knowledge context
|
|
197
|
+
Policies
|
|
198
|
+
Output schema
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
The agent then runs autonomously:
|
|
202
|
+
|
|
203
|
+
```text
|
|
204
|
+
Plan
|
|
205
|
+
Act
|
|
206
|
+
Use tool
|
|
207
|
+
Observe
|
|
208
|
+
Re-plan
|
|
209
|
+
Complete
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The workflow engine or UI does not need to understand the internal reasoning loop.
|
|
213
|
+
|
|
214
|
+
It only receives the final structured result.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Test Interface
|
|
219
|
+
|
|
220
|
+
Once an agent is configured, users can test it inside SolidX.
|
|
221
|
+
|
|
222
|
+
Default interface:
|
|
223
|
+
|
|
224
|
+
```text
|
|
225
|
+
Chat-based test console
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Future interfaces:
|
|
229
|
+
|
|
230
|
+
```text
|
|
231
|
+
Voice
|
|
232
|
+
WhatsApp
|
|
233
|
+
Slack
|
|
234
|
+
Email
|
|
235
|
+
Embedded app widget
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
This allows business users and developers to validate the agent before attaching it to workflows.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Workflow Integration
|
|
243
|
+
|
|
244
|
+
Any SolidX workflow can include an Agent Handoff step.
|
|
245
|
+
|
|
246
|
+
Example:
|
|
247
|
+
|
|
248
|
+
```text
|
|
249
|
+
Workflow Step:
|
|
250
|
+
Delegate document review to Document Review Agent
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
The workflow creates an Agent Run.
|
|
254
|
+
|
|
255
|
+
The agent works asynchronously.
|
|
256
|
+
|
|
257
|
+
When completed, it sends back a result.
|
|
258
|
+
|
|
259
|
+
The workflow then continues.
|
|
260
|
+
|
|
261
|
+
```text
|
|
262
|
+
Workflow
|
|
263
|
+
↓
|
|
264
|
+
Agent Handoff
|
|
265
|
+
↓
|
|
266
|
+
Mini-SWE Agent Run
|
|
267
|
+
↓
|
|
268
|
+
Agent Result
|
|
269
|
+
↓
|
|
270
|
+
Workflow Continues
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Strategic Value
|
|
276
|
+
|
|
277
|
+
This makes SolidX an enterprise agent platform, not just a workflow or low-code platform.
|
|
278
|
+
|
|
279
|
+
The key advantage is that agents become:
|
|
280
|
+
|
|
281
|
+
```text
|
|
282
|
+
Configurable
|
|
283
|
+
Reusable
|
|
284
|
+
Governed
|
|
285
|
+
Auditable
|
|
286
|
+
Workflow-aware
|
|
287
|
+
Tool-aware
|
|
288
|
+
Knowledge-aware
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
SolidX remains the control plane.
|
|
292
|
+
|
|
293
|
+
Mini-SWE becomes the execution loop.
|
|
294
|
+
|
|
295
|
+
Tools provide actions.
|
|
296
|
+
|
|
297
|
+
Skills provide task expertise.
|
|
298
|
+
|
|
299
|
+
Knowledge bases provide enterprise context.
|
|
300
|
+
|
|
301
|
+
Together, this creates a metadata-driven framework for building and deploying enterprise-grade AI agents.
|