@cernion/openclaw-energy-tools-sidecar 0.1.0
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/CHANGELOG.md +12 -0
- package/CONTRIBUTING.md +31 -0
- package/LICENSE +201 -0
- package/README.md +202 -0
- package/SECURITY.md +27 -0
- package/dist/index.d.ts +107 -0
- package/dist/index.js +1053 -0
- package/docker/.env.example +40 -0
- package/docker/Dockerfile +36 -0
- package/docker/README.md +302 -0
- package/docker/compose.sidecar-it.yml +49 -0
- package/docker/compose.yml +29 -0
- package/docker/entrypoint.sh +249 -0
- package/docker/profiles/cernion-demo/AGENTS.md +45 -0
- package/docker/profiles/cernion-demo/SOUL.md +23 -0
- package/docker/profiles/cernion-demo/TOOLS.md +111 -0
- package/openclaw.plugin.json +136 -0
- package/package.json +52 -0
- package/scripts/sidecar-smoke.mjs +127 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { executeRestExecutionPlan, queryDomainKnowledge, requestCernion } from "../dist/index.js";
|
|
3
|
+
|
|
4
|
+
const baseUrl = process.env.CERNION_BASE_URL || "http://10.0.0.8:3900";
|
|
5
|
+
const bearerTokenFile = process.env.CERNION_READONLY_TOKEN_FILE || "/run/secrets/cernion-readonly-token";
|
|
6
|
+
const timeoutMs = Number(process.env.CERNION_SIDECAR_TIMEOUT_MS || 15000);
|
|
7
|
+
const smokeAssetPath = process.env.CERNION_SMOKE_ASSET_PATH || "/api/assets/solar";
|
|
8
|
+
const smokeAssetLocation = process.env.CERNION_SMOKE_ASSET_LOCATION || "74909";
|
|
9
|
+
const smokeAssetLimit = Number(process.env.CERNION_SMOKE_ASSET_LIMIT || 3);
|
|
10
|
+
|
|
11
|
+
const config = {
|
|
12
|
+
baseUrl,
|
|
13
|
+
timeoutMs,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
if (process.env.CERNION_READONLY_TOKEN || process.env.CERNION_TOKEN) {
|
|
17
|
+
if (!process.env.CERNION_READONLY_TOKEN && process.env.CERNION_TOKEN) {
|
|
18
|
+
process.env.CERNION_READONLY_TOKEN = process.env.CERNION_TOKEN;
|
|
19
|
+
}
|
|
20
|
+
config.bearerTokenEnv = "CERNION_READONLY_TOKEN";
|
|
21
|
+
} else {
|
|
22
|
+
config.bearerTokenFile = bearerTokenFile;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function assertNoSecretEcho(value) {
|
|
26
|
+
const serialized = JSON.stringify(value);
|
|
27
|
+
assert(!/Bearer\s+[A-Za-z0-9._~+/=-]+/.test(serialized), "response must not echo an Authorization bearer header");
|
|
28
|
+
assert(!/ck_(live|readonly|test)_[A-Za-z0-9._~+/=-]+/.test(serialized), "response must not echo token-shaped secrets");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const descriptor = await requestCernion(config, "/api/agent-sidecar/descriptor");
|
|
32
|
+
assert.equal(descriptor?.provider?.id, "cernion");
|
|
33
|
+
assert.equal(descriptor?.sideEffects, "none");
|
|
34
|
+
assert(Array.isArray(descriptor?.tools), "descriptor.tools must be an array");
|
|
35
|
+
assert(descriptor.tools.length >= 3, "descriptor must expose at least the MVP tools");
|
|
36
|
+
assertNoSecretEcho(descriptor);
|
|
37
|
+
|
|
38
|
+
const tools = await requestCernion(config, "/api/agent-sidecar/mcp/tools");
|
|
39
|
+
assert.equal(tools?.provider?.id, "cernion");
|
|
40
|
+
assert(Array.isArray(tools?.tools), "tools.tools must be an array");
|
|
41
|
+
assert(tools.tools.some((tool) => tool.name === "cernion.list_readonly_capabilities"));
|
|
42
|
+
assertNoSecretEcho(tools);
|
|
43
|
+
|
|
44
|
+
const call = await requestCernion(config, "/api/agent-sidecar/mcp/tools/cernion.list_readonly_capabilities/call", {
|
|
45
|
+
method: "POST",
|
|
46
|
+
body: {
|
|
47
|
+
arguments: {
|
|
48
|
+
context: {
|
|
49
|
+
tenantId: "public",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
assertNoSecretEcho(call);
|
|
55
|
+
assert.notEqual(call?.error?.code, "sidecar_policy_blocked", "read-only capabilities call should not be policy-blocked");
|
|
56
|
+
|
|
57
|
+
const capabilities = await requestCernion(config, "/api/_agent/capabilities");
|
|
58
|
+
assert.equal(capabilities?.success, true);
|
|
59
|
+
assert(Array.isArray(capabilities?.data), "capabilities.data must be an array");
|
|
60
|
+
assertNoSecretEcho(capabilities);
|
|
61
|
+
|
|
62
|
+
const operations = await requestCernion(config, "/api/_agent/operations");
|
|
63
|
+
assert.equal(operations?.success, true);
|
|
64
|
+
assert(Array.isArray(operations?.data), "operations.data must be an array");
|
|
65
|
+
assert(
|
|
66
|
+
operations.data.every((operation) => Array.isArray(operation.aliases)),
|
|
67
|
+
"operations entries must include aliases arrays",
|
|
68
|
+
);
|
|
69
|
+
assertNoSecretEcho(operations);
|
|
70
|
+
|
|
71
|
+
const assetList = await executeRestExecutionPlan(config, {
|
|
72
|
+
method: "GET",
|
|
73
|
+
path: smokeAssetPath,
|
|
74
|
+
query: {
|
|
75
|
+
location: smokeAssetLocation,
|
|
76
|
+
limit: smokeAssetLimit,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
assert.notEqual(assetList?.isError, true, "asset-list REST plan must not return a Cernion error");
|
|
80
|
+
assertNoSecretEcho(assetList);
|
|
81
|
+
if (assetList?._sidecar) {
|
|
82
|
+
assert.equal(assetList._sidecar.assetListPagination?.requestedLimit, smokeAssetLimit);
|
|
83
|
+
assert(Array.isArray(assetList._sidecar.exportOptions), "asset-list sidecar metadata must include exportOptions");
|
|
84
|
+
assert(
|
|
85
|
+
assetList._sidecar.exportOptions.some((option) => option.format === "csv") &&
|
|
86
|
+
assetList._sidecar.exportOptions.some((option) => option.format === "xls"),
|
|
87
|
+
"asset-list exportOptions must include csv and xls",
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const fachwissen = await queryDomainKnowledge(config, {
|
|
92
|
+
query: "Welche Pflichten ergeben sich aus §14a EnWG für einen Verteilnetzbetreiber?",
|
|
93
|
+
limit: 3,
|
|
94
|
+
maxWaitMs: timeoutMs,
|
|
95
|
+
});
|
|
96
|
+
assert.equal(fachwissen?.kind, "domain_knowledge");
|
|
97
|
+
assert.equal(fachwissen?.source, "knowledge_rag");
|
|
98
|
+
assert.notEqual(fachwissen?.result?.isError, true, "Knowledge RAG query must not return a Cernion error");
|
|
99
|
+
assertNoSecretEcho(fachwissen);
|
|
100
|
+
|
|
101
|
+
const knowledgeRagReturned =
|
|
102
|
+
fachwissen?.result?.data?.returned ||
|
|
103
|
+
fachwissen?.result?.data?.results?.length ||
|
|
104
|
+
fachwissen?.result?.results?.length ||
|
|
105
|
+
0;
|
|
106
|
+
|
|
107
|
+
console.log(
|
|
108
|
+
JSON.stringify(
|
|
109
|
+
{
|
|
110
|
+
ok: true,
|
|
111
|
+
provider: descriptor.provider.id,
|
|
112
|
+
descriptorTools: descriptor.tools.length,
|
|
113
|
+
mcpTools: tools.tools.length,
|
|
114
|
+
capabilities: capabilities.data.length,
|
|
115
|
+
operations: operations.data.length,
|
|
116
|
+
assetSmoke: {
|
|
117
|
+
path: smokeAssetPath,
|
|
118
|
+
location: smokeAssetLocation,
|
|
119
|
+
limit: smokeAssetLimit,
|
|
120
|
+
sidecarGuidance: Boolean(assetList?._sidecar),
|
|
121
|
+
},
|
|
122
|
+
knowledgeRagReturned,
|
|
123
|
+
},
|
|
124
|
+
null,
|
|
125
|
+
2,
|
|
126
|
+
),
|
|
127
|
+
);
|