@electric-ax/agents-server-conformance-tests 0.1.9 → 0.1.11
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/index.cjs +71 -39
- package/dist/index.d.cts +1 -4
- package/dist/index.d.ts +1 -4
- package/dist/index.js +71 -39
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -328,11 +328,8 @@ var ElectricAgentsScenario = class {
|
|
|
328
328
|
});
|
|
329
329
|
return this;
|
|
330
330
|
}
|
|
331
|
-
readStream(
|
|
332
|
-
this.steps.push({
|
|
333
|
-
kind: `readStream`,
|
|
334
|
-
stream
|
|
335
|
-
});
|
|
331
|
+
readStream() {
|
|
332
|
+
this.steps.push({ kind: `readStream` });
|
|
336
333
|
return this;
|
|
337
334
|
}
|
|
338
335
|
list(filter) {
|
|
@@ -671,7 +668,6 @@ async function executeStep(ctx, step) {
|
|
|
671
668
|
(0, vitest.expect)(entity, `Webhook payload must contain entity context`).toBeDefined();
|
|
672
669
|
(0, vitest.expect)(entity.url).toBeTruthy();
|
|
673
670
|
(0, vitest.expect)(entity.streams.main).toBeTruthy();
|
|
674
|
-
(0, vitest.expect)(entity.streams.error).toBeTruthy();
|
|
675
671
|
if (step.checks?.url) (0, vitest.expect)(entity.url).toBe(step.checks.url);
|
|
676
672
|
if (step.checks?.type) (0, vitest.expect)(entity.type).toBe(step.checks.type);
|
|
677
673
|
if (step.checks?.status) (0, vitest.expect)(entity.status).toBe(step.checks.status);
|
|
@@ -712,7 +708,7 @@ async function executeStep(ctx, step) {
|
|
|
712
708
|
}
|
|
713
709
|
case `readStream`: {
|
|
714
710
|
if (!ctx.currentEntityStreams) throw new Error(`No current entity streams`);
|
|
715
|
-
const streamPath =
|
|
711
|
+
const streamPath = ctx.currentEntityStreams.main;
|
|
716
712
|
const res = await fetch(appendPathToUrl(ctx.baseUrl, `${streamPath}?offset=0000000000000000_0000000000000000`));
|
|
717
713
|
if (res.status === 200) {
|
|
718
714
|
const text = await res.text();
|
|
@@ -1207,14 +1203,11 @@ function checkEntityContextOnWebhook(history) {
|
|
|
1207
1203
|
}
|
|
1208
1204
|
}
|
|
1209
1205
|
/**
|
|
1210
|
-
* Spec S5 — Structural: stream
|
|
1206
|
+
* Spec S5 — Structural: stream path must match {entity.url}/main.
|
|
1211
1207
|
* Soundness: Sound | Completeness: Complete (within trace)
|
|
1212
1208
|
*/
|
|
1213
1209
|
function checkStreamPathsMatchEntityUrl(history) {
|
|
1214
|
-
for (const event of history) if (event.type === `entity_spawned`) {
|
|
1215
|
-
(0, vitest.expect)(event.streams.main).toBe(`${event.entityUrl}/main`);
|
|
1216
|
-
(0, vitest.expect)(event.streams.error).toBe(`${event.entityUrl}/error`);
|
|
1217
|
-
}
|
|
1210
|
+
for (const event of history) if (event.type === `entity_spawned`) (0, vitest.expect)(event.streams.main).toBe(`${event.entityUrl}/main`);
|
|
1218
1211
|
}
|
|
1219
1212
|
/**
|
|
1220
1213
|
* Spec S4 — Safety: entity status transitions must be valid.
|
|
@@ -1276,8 +1269,8 @@ function checkAdditiveSchemaEvolution(history) {
|
|
|
1276
1269
|
}
|
|
1277
1270
|
/**
|
|
1278
1271
|
* Spec R4 — Registry stream consistency: every entity_spawned must refer to an
|
|
1279
|
-
* entity that has not already been killed, and stream
|
|
1280
|
-
* convention {entity.url}/main
|
|
1272
|
+
* entity that has not already been killed, and stream URL must follow the
|
|
1273
|
+
* convention {entity.url}/main.
|
|
1281
1274
|
* Soundness: Sound | Completeness: Complete (within trace)
|
|
1282
1275
|
*/
|
|
1283
1276
|
function checkRegistryStreamConsistency(history) {
|
|
@@ -1287,7 +1280,6 @@ function checkRegistryStreamConsistency(history) {
|
|
|
1287
1280
|
if (event.type === `entity_spawned`) {
|
|
1288
1281
|
(0, vitest.expect)(!killedEntities.has(event.entityUrl), `Registry consistency: entity_spawned for ${event.entityUrl} but entity was already killed`).toBe(true);
|
|
1289
1282
|
(0, vitest.expect)(event.streams.main, `Registry consistency: entity ${event.entityUrl} streams.main must be ${event.entityUrl}/main`).toBe(`${event.entityUrl}/main`);
|
|
1290
|
-
(0, vitest.expect)(event.streams.error, `Registry consistency: entity ${event.entityUrl} streams.error must be ${event.entityUrl}/error`).toBe(`${event.entityUrl}/error`);
|
|
1291
1283
|
}
|
|
1292
1284
|
}
|
|
1293
1285
|
}
|
|
@@ -1832,8 +1824,6 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
1832
1824
|
}).spawn(`spawn-test-agent`, `entity-1`).expectStatus(`running`).custom(async (ctx) => {
|
|
1833
1825
|
const mainRes = await fetch(appendPathToUrl(ctx.baseUrl, ctx.currentEntityStreams.main), { method: `HEAD` });
|
|
1834
1826
|
(0, vitest.expect)(mainRes.status).toBe(200);
|
|
1835
|
-
const errorRes = await fetch(appendPathToUrl(ctx.baseUrl, ctx.currentEntityStreams.error), { method: `HEAD` });
|
|
1836
|
-
(0, vitest.expect)(errorRes.status).toBe(200);
|
|
1837
1827
|
}).run());
|
|
1838
1828
|
(0, vitest.test)(`spawn at unregistered type returns UNKNOWN_ENTITY_TYPE`, () => electricAgents(config.baseUrl).custom(async (ctx) => {
|
|
1839
1829
|
const res = await fetch(appendPathToUrl(ctx.baseUrl, routeControlPlanePath(`/unregistered/entity-1`)), {
|
|
@@ -2904,25 +2894,51 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
2904
2894
|
const block = result.content[0];
|
|
2905
2895
|
return block?.type === `text` && block.text ? block.text : ``;
|
|
2906
2896
|
}
|
|
2897
|
+
async function makeSandbox(workingDirectory) {
|
|
2898
|
+
const { unrestrictedSandbox } = await import(`../../agents-runtime/src/sandbox/unrestricted`);
|
|
2899
|
+
return unrestrictedSandbox({ workingDirectory });
|
|
2900
|
+
}
|
|
2907
2901
|
(0, vitest.test)(`bash tool captures stdout and stderr`, async () => {
|
|
2908
2902
|
const { createBashTool } = await import(`../../agents-runtime/src/tools`);
|
|
2909
|
-
const
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2903
|
+
const sandbox = await makeSandbox(`/tmp`);
|
|
2904
|
+
try {
|
|
2905
|
+
const tool = createBashTool(sandbox);
|
|
2906
|
+
const result = await tool.execute(`test-tc`, { command: `echo "hello" && echo "error" >&2` });
|
|
2907
|
+
(0, vitest.expect)(firstText(result)).toContain(`hello`);
|
|
2908
|
+
(0, vitest.expect)(firstText(result)).toContain(`error`);
|
|
2909
|
+
(0, vitest.expect)(result.details.exitCode).toBe(0);
|
|
2910
|
+
} finally {
|
|
2911
|
+
await sandbox.dispose();
|
|
2912
|
+
}
|
|
2914
2913
|
});
|
|
2915
2914
|
(0, vitest.test)(`bash tool enforces timeout`, async () => {
|
|
2916
2915
|
const { createBashTool } = await import(`../../agents-runtime/src/tools`);
|
|
2917
|
-
const
|
|
2918
|
-
|
|
2919
|
-
|
|
2916
|
+
const sandbox = await makeSandbox(`/tmp`);
|
|
2917
|
+
try {
|
|
2918
|
+
const tool = createBashTool(sandbox);
|
|
2919
|
+
const result = await tool.execute(`test-tc`, { command: `sleep 60` });
|
|
2920
|
+
(0, vitest.expect)(result.details.timedOut).toBe(true);
|
|
2921
|
+
} finally {
|
|
2922
|
+
await sandbox.dispose();
|
|
2923
|
+
}
|
|
2920
2924
|
}, 35e3);
|
|
2921
2925
|
(0, vitest.test)(`read_file rejects paths outside working directory`, async () => {
|
|
2922
2926
|
const { createReadFileTool } = await import(`../../agents-runtime/src/tools`);
|
|
2923
|
-
const
|
|
2924
|
-
const
|
|
2925
|
-
|
|
2927
|
+
const fs = await import(`node:fs/promises`);
|
|
2928
|
+
const dir = `/tmp/test-workdir-${Date.now()}`;
|
|
2929
|
+
await fs.mkdir(dir, { recursive: true });
|
|
2930
|
+
const sandbox = await makeSandbox(dir);
|
|
2931
|
+
try {
|
|
2932
|
+
const tool = createReadFileTool(sandbox);
|
|
2933
|
+
const result = await tool.execute(`test-tc`, { path: `../../etc/passwd` });
|
|
2934
|
+
(0, vitest.expect)(firstText(result)).toContain(`outside the working directory`);
|
|
2935
|
+
} finally {
|
|
2936
|
+
await sandbox.dispose();
|
|
2937
|
+
await fs.rm(dir, {
|
|
2938
|
+
recursive: true,
|
|
2939
|
+
force: true
|
|
2940
|
+
});
|
|
2941
|
+
}
|
|
2926
2942
|
});
|
|
2927
2943
|
(0, vitest.test)(`read_file rejects binary files`, async () => {
|
|
2928
2944
|
const { createReadFileTool } = await import(`../../agents-runtime/src/tools`);
|
|
@@ -2937,10 +2953,15 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
2937
2953
|
2,
|
|
2938
2954
|
255
|
|
2939
2955
|
]));
|
|
2940
|
-
const
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2956
|
+
const sandbox = await makeSandbox(dir);
|
|
2957
|
+
try {
|
|
2958
|
+
const tool = createReadFileTool(sandbox);
|
|
2959
|
+
const result = await tool.execute(`test-tc`, { path: `test.bin` });
|
|
2960
|
+
(0, vitest.expect)(firstText(result)).toContain(`binary file`);
|
|
2961
|
+
} finally {
|
|
2962
|
+
await sandbox.dispose();
|
|
2963
|
+
await fs.rm(dir, { recursive: true });
|
|
2964
|
+
}
|
|
2944
2965
|
});
|
|
2945
2966
|
(0, vitest.test)(`read_file rejects oversized files`, async () => {
|
|
2946
2967
|
const { createReadFileTool } = await import(`../../agents-runtime/src/tools`);
|
|
@@ -2950,10 +2971,15 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
2950
2971
|
await fs.mkdir(dir, { recursive: true });
|
|
2951
2972
|
const bigPath = path.join(dir, `big.txt`);
|
|
2952
2973
|
await fs.writeFile(bigPath, `x`.repeat(600 * 1024));
|
|
2953
|
-
const
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2974
|
+
const sandbox = await makeSandbox(dir);
|
|
2975
|
+
try {
|
|
2976
|
+
const tool = createReadFileTool(sandbox);
|
|
2977
|
+
const result = await tool.execute(`test-tc`, { path: `big.txt` });
|
|
2978
|
+
(0, vitest.expect)(firstText(result)).toContain(`too large`);
|
|
2979
|
+
} finally {
|
|
2980
|
+
await sandbox.dispose();
|
|
2981
|
+
await fs.rm(dir, { recursive: true });
|
|
2982
|
+
}
|
|
2957
2983
|
});
|
|
2958
2984
|
(0, vitest.test)(`web_search tool has correct interface`, async () => {
|
|
2959
2985
|
const { braveSearchTool } = await import(`../../agents-runtime/src/tools`);
|
|
@@ -2961,9 +2987,15 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
2961
2987
|
(0, vitest.expect)(typeof braveSearchTool.execute).toBe(`function`);
|
|
2962
2988
|
});
|
|
2963
2989
|
(0, vitest.test)(`fetch_url tool has correct interface`, async () => {
|
|
2964
|
-
const {
|
|
2965
|
-
|
|
2966
|
-
|
|
2990
|
+
const { createFetchUrlTool } = await import(`../../agents-runtime/src/tools`);
|
|
2991
|
+
const sandbox = await makeSandbox(`/tmp`);
|
|
2992
|
+
try {
|
|
2993
|
+
const tool = createFetchUrlTool(sandbox);
|
|
2994
|
+
(0, vitest.expect)(tool.name).toBe(`fetch_url`);
|
|
2995
|
+
(0, vitest.expect)(typeof tool.execute).toBe(`function`);
|
|
2996
|
+
} finally {
|
|
2997
|
+
await sandbox.dispose();
|
|
2998
|
+
}
|
|
2967
2999
|
});
|
|
2968
3000
|
});
|
|
2969
3001
|
(0, vitest.describe)(`Electric Agents Send — State Protocol Format`, () => {
|
package/dist/index.d.cts
CHANGED
|
@@ -64,7 +64,6 @@ type HistoryEvent = {
|
|
|
64
64
|
status: string;
|
|
65
65
|
streams: {
|
|
66
66
|
main: string;
|
|
67
|
-
error: string;
|
|
68
67
|
};
|
|
69
68
|
parent?: string;
|
|
70
69
|
} | {
|
|
@@ -184,7 +183,6 @@ interface WebhookEntityContext {
|
|
|
184
183
|
url: string;
|
|
185
184
|
streams: {
|
|
186
185
|
main: string;
|
|
187
|
-
error: string;
|
|
188
186
|
};
|
|
189
187
|
tags?: Record<string, string>;
|
|
190
188
|
}
|
|
@@ -273,7 +271,6 @@ interface RunContext {
|
|
|
273
271
|
currentEntityUrl: string | null;
|
|
274
272
|
currentEntityStreams: {
|
|
275
273
|
main: string;
|
|
276
|
-
error: string;
|
|
277
274
|
} | null;
|
|
278
275
|
currentWriteToken: string | null;
|
|
279
276
|
notification: WebhookNotification | null;
|
|
@@ -320,7 +317,7 @@ declare class ElectricAgentsScenario {
|
|
|
320
317
|
expectEntityContext(checks?: EntityContextChecks): this;
|
|
321
318
|
expectStatus(status: string): this;
|
|
322
319
|
expectStreamContains(messageType: string): this;
|
|
323
|
-
readStream(
|
|
320
|
+
readStream(): this;
|
|
324
321
|
list(filter?: {
|
|
325
322
|
type?: string;
|
|
326
323
|
status?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -64,7 +64,6 @@ type HistoryEvent = {
|
|
|
64
64
|
status: string;
|
|
65
65
|
streams: {
|
|
66
66
|
main: string;
|
|
67
|
-
error: string;
|
|
68
67
|
};
|
|
69
68
|
parent?: string;
|
|
70
69
|
} | {
|
|
@@ -184,7 +183,6 @@ interface WebhookEntityContext {
|
|
|
184
183
|
url: string;
|
|
185
184
|
streams: {
|
|
186
185
|
main: string;
|
|
187
|
-
error: string;
|
|
188
186
|
};
|
|
189
187
|
tags?: Record<string, string>;
|
|
190
188
|
}
|
|
@@ -273,7 +271,6 @@ interface RunContext {
|
|
|
273
271
|
currentEntityUrl: string | null;
|
|
274
272
|
currentEntityStreams: {
|
|
275
273
|
main: string;
|
|
276
|
-
error: string;
|
|
277
274
|
} | null;
|
|
278
275
|
currentWriteToken: string | null;
|
|
279
276
|
notification: WebhookNotification | null;
|
|
@@ -320,7 +317,7 @@ declare class ElectricAgentsScenario {
|
|
|
320
317
|
expectEntityContext(checks?: EntityContextChecks): this;
|
|
321
318
|
expectStatus(status: string): this;
|
|
322
319
|
expectStreamContains(messageType: string): this;
|
|
323
|
-
readStream(
|
|
320
|
+
readStream(): this;
|
|
324
321
|
list(filter?: {
|
|
325
322
|
type?: string;
|
|
326
323
|
status?: string;
|
package/dist/index.js
CHANGED
|
@@ -304,11 +304,8 @@ var ElectricAgentsScenario = class {
|
|
|
304
304
|
});
|
|
305
305
|
return this;
|
|
306
306
|
}
|
|
307
|
-
readStream(
|
|
308
|
-
this.steps.push({
|
|
309
|
-
kind: `readStream`,
|
|
310
|
-
stream
|
|
311
|
-
});
|
|
307
|
+
readStream() {
|
|
308
|
+
this.steps.push({ kind: `readStream` });
|
|
312
309
|
return this;
|
|
313
310
|
}
|
|
314
311
|
list(filter) {
|
|
@@ -647,7 +644,6 @@ async function executeStep(ctx, step) {
|
|
|
647
644
|
expect(entity, `Webhook payload must contain entity context`).toBeDefined();
|
|
648
645
|
expect(entity.url).toBeTruthy();
|
|
649
646
|
expect(entity.streams.main).toBeTruthy();
|
|
650
|
-
expect(entity.streams.error).toBeTruthy();
|
|
651
647
|
if (step.checks?.url) expect(entity.url).toBe(step.checks.url);
|
|
652
648
|
if (step.checks?.type) expect(entity.type).toBe(step.checks.type);
|
|
653
649
|
if (step.checks?.status) expect(entity.status).toBe(step.checks.status);
|
|
@@ -688,7 +684,7 @@ async function executeStep(ctx, step) {
|
|
|
688
684
|
}
|
|
689
685
|
case `readStream`: {
|
|
690
686
|
if (!ctx.currentEntityStreams) throw new Error(`No current entity streams`);
|
|
691
|
-
const streamPath =
|
|
687
|
+
const streamPath = ctx.currentEntityStreams.main;
|
|
692
688
|
const res = await fetch(appendPathToUrl(ctx.baseUrl, `${streamPath}?offset=0000000000000000_0000000000000000`));
|
|
693
689
|
if (res.status === 200) {
|
|
694
690
|
const text = await res.text();
|
|
@@ -1183,14 +1179,11 @@ function checkEntityContextOnWebhook(history) {
|
|
|
1183
1179
|
}
|
|
1184
1180
|
}
|
|
1185
1181
|
/**
|
|
1186
|
-
* Spec S5 — Structural: stream
|
|
1182
|
+
* Spec S5 — Structural: stream path must match {entity.url}/main.
|
|
1187
1183
|
* Soundness: Sound | Completeness: Complete (within trace)
|
|
1188
1184
|
*/
|
|
1189
1185
|
function checkStreamPathsMatchEntityUrl(history) {
|
|
1190
|
-
for (const event of history) if (event.type === `entity_spawned`) {
|
|
1191
|
-
expect(event.streams.main).toBe(`${event.entityUrl}/main`);
|
|
1192
|
-
expect(event.streams.error).toBe(`${event.entityUrl}/error`);
|
|
1193
|
-
}
|
|
1186
|
+
for (const event of history) if (event.type === `entity_spawned`) expect(event.streams.main).toBe(`${event.entityUrl}/main`);
|
|
1194
1187
|
}
|
|
1195
1188
|
/**
|
|
1196
1189
|
* Spec S4 — Safety: entity status transitions must be valid.
|
|
@@ -1252,8 +1245,8 @@ function checkAdditiveSchemaEvolution(history) {
|
|
|
1252
1245
|
}
|
|
1253
1246
|
/**
|
|
1254
1247
|
* Spec R4 — Registry stream consistency: every entity_spawned must refer to an
|
|
1255
|
-
* entity that has not already been killed, and stream
|
|
1256
|
-
* convention {entity.url}/main
|
|
1248
|
+
* entity that has not already been killed, and stream URL must follow the
|
|
1249
|
+
* convention {entity.url}/main.
|
|
1257
1250
|
* Soundness: Sound | Completeness: Complete (within trace)
|
|
1258
1251
|
*/
|
|
1259
1252
|
function checkRegistryStreamConsistency(history) {
|
|
@@ -1263,7 +1256,6 @@ function checkRegistryStreamConsistency(history) {
|
|
|
1263
1256
|
if (event.type === `entity_spawned`) {
|
|
1264
1257
|
expect(!killedEntities.has(event.entityUrl), `Registry consistency: entity_spawned for ${event.entityUrl} but entity was already killed`).toBe(true);
|
|
1265
1258
|
expect(event.streams.main, `Registry consistency: entity ${event.entityUrl} streams.main must be ${event.entityUrl}/main`).toBe(`${event.entityUrl}/main`);
|
|
1266
|
-
expect(event.streams.error, `Registry consistency: entity ${event.entityUrl} streams.error must be ${event.entityUrl}/error`).toBe(`${event.entityUrl}/error`);
|
|
1267
1259
|
}
|
|
1268
1260
|
}
|
|
1269
1261
|
}
|
|
@@ -1808,8 +1800,6 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
1808
1800
|
}).spawn(`spawn-test-agent`, `entity-1`).expectStatus(`running`).custom(async (ctx) => {
|
|
1809
1801
|
const mainRes = await fetch(appendPathToUrl(ctx.baseUrl, ctx.currentEntityStreams.main), { method: `HEAD` });
|
|
1810
1802
|
expect(mainRes.status).toBe(200);
|
|
1811
|
-
const errorRes = await fetch(appendPathToUrl(ctx.baseUrl, ctx.currentEntityStreams.error), { method: `HEAD` });
|
|
1812
|
-
expect(errorRes.status).toBe(200);
|
|
1813
1803
|
}).run());
|
|
1814
1804
|
test(`spawn at unregistered type returns UNKNOWN_ENTITY_TYPE`, () => electricAgents(config.baseUrl).custom(async (ctx) => {
|
|
1815
1805
|
const res = await fetch(appendPathToUrl(ctx.baseUrl, routeControlPlanePath(`/unregistered/entity-1`)), {
|
|
@@ -2880,25 +2870,51 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
2880
2870
|
const block = result.content[0];
|
|
2881
2871
|
return block?.type === `text` && block.text ? block.text : ``;
|
|
2882
2872
|
}
|
|
2873
|
+
async function makeSandbox(workingDirectory) {
|
|
2874
|
+
const { unrestrictedSandbox } = await import(`../../agents-runtime/src/sandbox/unrestricted`);
|
|
2875
|
+
return unrestrictedSandbox({ workingDirectory });
|
|
2876
|
+
}
|
|
2883
2877
|
test(`bash tool captures stdout and stderr`, async () => {
|
|
2884
2878
|
const { createBashTool } = await import(`../../agents-runtime/src/tools`);
|
|
2885
|
-
const
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2879
|
+
const sandbox = await makeSandbox(`/tmp`);
|
|
2880
|
+
try {
|
|
2881
|
+
const tool = createBashTool(sandbox);
|
|
2882
|
+
const result = await tool.execute(`test-tc`, { command: `echo "hello" && echo "error" >&2` });
|
|
2883
|
+
expect(firstText(result)).toContain(`hello`);
|
|
2884
|
+
expect(firstText(result)).toContain(`error`);
|
|
2885
|
+
expect(result.details.exitCode).toBe(0);
|
|
2886
|
+
} finally {
|
|
2887
|
+
await sandbox.dispose();
|
|
2888
|
+
}
|
|
2890
2889
|
});
|
|
2891
2890
|
test(`bash tool enforces timeout`, async () => {
|
|
2892
2891
|
const { createBashTool } = await import(`../../agents-runtime/src/tools`);
|
|
2893
|
-
const
|
|
2894
|
-
|
|
2895
|
-
|
|
2892
|
+
const sandbox = await makeSandbox(`/tmp`);
|
|
2893
|
+
try {
|
|
2894
|
+
const tool = createBashTool(sandbox);
|
|
2895
|
+
const result = await tool.execute(`test-tc`, { command: `sleep 60` });
|
|
2896
|
+
expect(result.details.timedOut).toBe(true);
|
|
2897
|
+
} finally {
|
|
2898
|
+
await sandbox.dispose();
|
|
2899
|
+
}
|
|
2896
2900
|
}, 35e3);
|
|
2897
2901
|
test(`read_file rejects paths outside working directory`, async () => {
|
|
2898
2902
|
const { createReadFileTool } = await import(`../../agents-runtime/src/tools`);
|
|
2899
|
-
const
|
|
2900
|
-
const
|
|
2901
|
-
|
|
2903
|
+
const fs = await import(`node:fs/promises`);
|
|
2904
|
+
const dir = `/tmp/test-workdir-${Date.now()}`;
|
|
2905
|
+
await fs.mkdir(dir, { recursive: true });
|
|
2906
|
+
const sandbox = await makeSandbox(dir);
|
|
2907
|
+
try {
|
|
2908
|
+
const tool = createReadFileTool(sandbox);
|
|
2909
|
+
const result = await tool.execute(`test-tc`, { path: `../../etc/passwd` });
|
|
2910
|
+
expect(firstText(result)).toContain(`outside the working directory`);
|
|
2911
|
+
} finally {
|
|
2912
|
+
await sandbox.dispose();
|
|
2913
|
+
await fs.rm(dir, {
|
|
2914
|
+
recursive: true,
|
|
2915
|
+
force: true
|
|
2916
|
+
});
|
|
2917
|
+
}
|
|
2902
2918
|
});
|
|
2903
2919
|
test(`read_file rejects binary files`, async () => {
|
|
2904
2920
|
const { createReadFileTool } = await import(`../../agents-runtime/src/tools`);
|
|
@@ -2913,10 +2929,15 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
2913
2929
|
2,
|
|
2914
2930
|
255
|
|
2915
2931
|
]));
|
|
2916
|
-
const
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2932
|
+
const sandbox = await makeSandbox(dir);
|
|
2933
|
+
try {
|
|
2934
|
+
const tool = createReadFileTool(sandbox);
|
|
2935
|
+
const result = await tool.execute(`test-tc`, { path: `test.bin` });
|
|
2936
|
+
expect(firstText(result)).toContain(`binary file`);
|
|
2937
|
+
} finally {
|
|
2938
|
+
await sandbox.dispose();
|
|
2939
|
+
await fs.rm(dir, { recursive: true });
|
|
2940
|
+
}
|
|
2920
2941
|
});
|
|
2921
2942
|
test(`read_file rejects oversized files`, async () => {
|
|
2922
2943
|
const { createReadFileTool } = await import(`../../agents-runtime/src/tools`);
|
|
@@ -2926,10 +2947,15 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
2926
2947
|
await fs.mkdir(dir, { recursive: true });
|
|
2927
2948
|
const bigPath = path.join(dir, `big.txt`);
|
|
2928
2949
|
await fs.writeFile(bigPath, `x`.repeat(600 * 1024));
|
|
2929
|
-
const
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2950
|
+
const sandbox = await makeSandbox(dir);
|
|
2951
|
+
try {
|
|
2952
|
+
const tool = createReadFileTool(sandbox);
|
|
2953
|
+
const result = await tool.execute(`test-tc`, { path: `big.txt` });
|
|
2954
|
+
expect(firstText(result)).toContain(`too large`);
|
|
2955
|
+
} finally {
|
|
2956
|
+
await sandbox.dispose();
|
|
2957
|
+
await fs.rm(dir, { recursive: true });
|
|
2958
|
+
}
|
|
2933
2959
|
});
|
|
2934
2960
|
test(`web_search tool has correct interface`, async () => {
|
|
2935
2961
|
const { braveSearchTool } = await import(`../../agents-runtime/src/tools`);
|
|
@@ -2937,9 +2963,15 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
2937
2963
|
expect(typeof braveSearchTool.execute).toBe(`function`);
|
|
2938
2964
|
});
|
|
2939
2965
|
test(`fetch_url tool has correct interface`, async () => {
|
|
2940
|
-
const {
|
|
2941
|
-
|
|
2942
|
-
|
|
2966
|
+
const { createFetchUrlTool } = await import(`../../agents-runtime/src/tools`);
|
|
2967
|
+
const sandbox = await makeSandbox(`/tmp`);
|
|
2968
|
+
try {
|
|
2969
|
+
const tool = createFetchUrlTool(sandbox);
|
|
2970
|
+
expect(tool.name).toBe(`fetch_url`);
|
|
2971
|
+
expect(typeof tool.execute).toBe(`function`);
|
|
2972
|
+
} finally {
|
|
2973
|
+
await sandbox.dispose();
|
|
2974
|
+
}
|
|
2943
2975
|
});
|
|
2944
2976
|
});
|
|
2945
2977
|
describe(`Electric Agents Send — State Protocol Format`, () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@electric-ax/agents-server-conformance-tests",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Conformance test suite for Electric Agents server implementations",
|
|
5
5
|
"author": "Durable Stream contributors",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@durable-streams/client": "^0.2.6",
|
|
38
|
-
"@electric-sql/client": "^1.5.
|
|
38
|
+
"@electric-sql/client": "^1.5.20",
|
|
39
39
|
"fast-check": "^4.6.0",
|
|
40
40
|
"vitest": "^4.1.0"
|
|
41
41
|
},
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@mariozechner/pi-agent-core": "^0.70.2",
|
|
44
44
|
"tsdown": "^0.9.0",
|
|
45
45
|
"typescript": "^5.0.0",
|
|
46
|
-
"@electric-ax/agents-server": "0.4.
|
|
46
|
+
"@electric-ax/agents-server": "0.4.16"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
|
49
49
|
"node": ">=18.0.0"
|