@alumnium/test 0.24.2 → 0.25.0-alpha.1
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/README.md +1 -1
- package/package.json +4 -3
- package/src/index.cjs +432 -28
- package/src/index.cjs.map +10 -5
- package/src/index.d.ts +4 -0
- package/src/index.js +435 -30
- package/src/index.js.map +10 -5
- package/src/masker/ToolCallMasker.d.ts +5 -1
- package/src/masker/alumniumMaskerProps.d.ts +16 -0
- package/src/player/BashExecutor.d.ts +16 -0
- package/src/player/McpExecutor.d.ts +37 -0
- package/src/player/Player.d.ts +53 -0
- package/src/player/ToolExecutor.d.ts +25 -0
package/src/index.js
CHANGED
|
@@ -500,6 +500,12 @@ class ToolCallMasker {
|
|
|
500
500
|
return [];
|
|
501
501
|
}
|
|
502
502
|
const json = parseJsonStructure(text);
|
|
503
|
+
if (!json && options.secretsOnly) {
|
|
504
|
+
logger4.debug(
|
|
505
|
+
`Failed tool call ${callId} output is not JSON, ignoring it`,
|
|
506
|
+
);
|
|
507
|
+
return [];
|
|
508
|
+
}
|
|
503
509
|
if (!json) {
|
|
504
510
|
logger4.info(
|
|
505
511
|
`Tool call ${callId} output is not a JSON object or array, ignoring it for masking. ` +
|
|
@@ -720,10 +726,418 @@ class Masker {
|
|
|
720
726
|
);
|
|
721
727
|
}
|
|
722
728
|
}
|
|
729
|
+
// test/src/player/BashExecutor.ts
|
|
730
|
+
import { spawn } from "node:child_process";
|
|
731
|
+
import z2 from "zod";
|
|
732
|
+
import { Telemetry as Telemetry5 } from "@alumnium/core/internal";
|
|
733
|
+
var { logger: logger5 } = Telemetry5.get(
|
|
734
|
+
"packages/typescript/test/src/player/BashExecutor.ts",
|
|
735
|
+
);
|
|
736
|
+
var BASH_TOOL = "Bash";
|
|
737
|
+
var DEFAULT_TIMEOUT_MS = 120000;
|
|
738
|
+
var BashInput = z2.object({
|
|
739
|
+
command: z2.string(),
|
|
740
|
+
timeout: z2.number().positive().optional(),
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
class BashExecutor {
|
|
744
|
+
#props;
|
|
745
|
+
constructor(props) {
|
|
746
|
+
this.#props = props;
|
|
747
|
+
}
|
|
748
|
+
handles(call) {
|
|
749
|
+
return !call.server && call.tool === BASH_TOOL;
|
|
750
|
+
}
|
|
751
|
+
async execute(call) {
|
|
752
|
+
const { command, timeout } = BashInput.parse(call.input);
|
|
753
|
+
if (!this.#allows(command))
|
|
754
|
+
throw new Error(`Command is not allowed to replay: ${command}`);
|
|
755
|
+
logger5.debug(`Running command: ${command}`);
|
|
756
|
+
const { code, stdout, stderr } = await this.#run(command, {
|
|
757
|
+
cwd: this.#props.cwd,
|
|
758
|
+
env: { ...process.env, ...this.#props.env },
|
|
759
|
+
timeoutMs: timeout ?? this.#props.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
760
|
+
});
|
|
761
|
+
if (code === 0) return { content: stdout, isError: false };
|
|
762
|
+
return {
|
|
763
|
+
content: [stdout, stderr, `Exit code ${code}`].filter(Boolean).join(`
|
|
764
|
+
`),
|
|
765
|
+
isError: true,
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
#allows(command) {
|
|
769
|
+
return this.#props.allow.some((allowed) =>
|
|
770
|
+
typeof allowed === "string"
|
|
771
|
+
? command.startsWith(allowed)
|
|
772
|
+
: allowed.test(command),
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
#run(command, options) {
|
|
776
|
+
return new Promise((resolve, reject) => {
|
|
777
|
+
const child = spawn("bash", ["-c", command], {
|
|
778
|
+
cwd: options.cwd,
|
|
779
|
+
env: options.env,
|
|
780
|
+
timeout: options.timeoutMs,
|
|
781
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
782
|
+
});
|
|
783
|
+
let stdout = "";
|
|
784
|
+
let stderr = "";
|
|
785
|
+
child.stdout.setEncoding("utf8").on("data", (data) => (stdout += data));
|
|
786
|
+
child.stderr.setEncoding("utf8").on("data", (data) => (stderr += data));
|
|
787
|
+
child.on("error", reject);
|
|
788
|
+
child.on("close", (code) => resolve({ code, stdout, stderr }));
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
// test/src/player/McpExecutor.ts
|
|
793
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
794
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
795
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
796
|
+
import {
|
|
797
|
+
ALUMNIUM_CORE_VERSION,
|
|
798
|
+
Telemetry as Telemetry6,
|
|
799
|
+
} from "@alumnium/core/internal";
|
|
800
|
+
var { logger: logger6 } = Telemetry6.get(
|
|
801
|
+
"packages/typescript/test/src/player/McpExecutor.ts",
|
|
802
|
+
);
|
|
803
|
+
var DEFAULT_RETRIES = 2;
|
|
804
|
+
var DEFAULT_RETRY_DELAY_MS = 1000;
|
|
805
|
+
|
|
806
|
+
class McpExecutor {
|
|
807
|
+
#servers;
|
|
808
|
+
#retries;
|
|
809
|
+
#retryDelayMs;
|
|
810
|
+
#clients = new Map();
|
|
811
|
+
constructor(props) {
|
|
812
|
+
this.#servers = props.servers;
|
|
813
|
+
this.#retries = props.retries ?? DEFAULT_RETRIES;
|
|
814
|
+
this.#retryDelayMs = props.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
815
|
+
}
|
|
816
|
+
handles(call) {
|
|
817
|
+
return Boolean(call.server && call.server in this.#servers);
|
|
818
|
+
}
|
|
819
|
+
async execute(call, options = {}) {
|
|
820
|
+
const toolKey = Recording.toolKey(call);
|
|
821
|
+
const client = await this.#client(call.server);
|
|
822
|
+
for (let attempt = 0; ; attempt++) {
|
|
823
|
+
const canRetry = attempt < this.#retries;
|
|
824
|
+
const delayMs = this.#retryDelayMs * 2 ** attempt;
|
|
825
|
+
try {
|
|
826
|
+
const result = await client.callTool({
|
|
827
|
+
name: call.tool,
|
|
828
|
+
arguments: call.input,
|
|
829
|
+
...(options.meta ? { _meta: options.meta } : {}),
|
|
830
|
+
});
|
|
831
|
+
const toolResult = {
|
|
832
|
+
content: result.content ?? [],
|
|
833
|
+
isError: Boolean(result.isError),
|
|
834
|
+
};
|
|
835
|
+
if (!toolResult.isError || !canRetry) return toolResult;
|
|
836
|
+
logger6.warn(`Tool '${toolKey}' reported an error, retrying`);
|
|
837
|
+
} catch (error) {
|
|
838
|
+
if (!canRetry) throw error;
|
|
839
|
+
logger6.warn(`Tool '${toolKey}' failed, retrying: {error}`, { error });
|
|
840
|
+
}
|
|
841
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
async close() {
|
|
845
|
+
const clients = [...this.#clients];
|
|
846
|
+
this.#clients.clear();
|
|
847
|
+
await Promise.all(
|
|
848
|
+
clients.map(async ([name, client]) => {
|
|
849
|
+
if (this.#servers[name]?.type === "client") return;
|
|
850
|
+
try {
|
|
851
|
+
await (await client).close();
|
|
852
|
+
} catch (error) {
|
|
853
|
+
logger6.warn(`Failed to close MCP server '${name}': {error}`, {
|
|
854
|
+
error,
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
}),
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
#client(name) {
|
|
861
|
+
let client = this.#clients.get(name);
|
|
862
|
+
if (!client) {
|
|
863
|
+
client = this.#connect(name);
|
|
864
|
+
client.catch(() => this.#clients.delete(name));
|
|
865
|
+
this.#clients.set(name, client);
|
|
866
|
+
}
|
|
867
|
+
return client;
|
|
868
|
+
}
|
|
869
|
+
async #connect(name) {
|
|
870
|
+
const server = this.#servers[name];
|
|
871
|
+
if (!server) throw new Error(`Unknown MCP server '${name}'`);
|
|
872
|
+
if (server.type === "client") return server.client;
|
|
873
|
+
logger6.debug(`Connecting to MCP server '${name}'`);
|
|
874
|
+
const transport =
|
|
875
|
+
server.type === "http"
|
|
876
|
+
? new StreamableHTTPClientTransport(new URL(server.url), {
|
|
877
|
+
requestInit: { headers: server.headers ?? {} },
|
|
878
|
+
})
|
|
879
|
+
: new StdioClientTransport({
|
|
880
|
+
command: server.command,
|
|
881
|
+
args: server.args ?? [],
|
|
882
|
+
env: { ...this.#definedEnv(), ...server.env },
|
|
883
|
+
...(server.cwd ? { cwd: server.cwd } : {}),
|
|
884
|
+
stderr: "inherit",
|
|
885
|
+
});
|
|
886
|
+
const client = new Client({
|
|
887
|
+
name: "alumnium-test-player",
|
|
888
|
+
version: ALUMNIUM_CORE_VERSION,
|
|
889
|
+
});
|
|
890
|
+
await client.connect(transport);
|
|
891
|
+
return client;
|
|
892
|
+
}
|
|
893
|
+
#definedEnv() {
|
|
894
|
+
return Object.fromEntries(
|
|
895
|
+
Object.entries(process.env).filter((entry) => entry[1] !== undefined),
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
// test/src/player/Player.ts
|
|
900
|
+
import z3 from "zod";
|
|
901
|
+
import { Env as Env2, Telemetry as Telemetry7 } from "@alumnium/core/internal";
|
|
902
|
+
|
|
903
|
+
// test/src/masker/alumniumMaskerProps.ts
|
|
904
|
+
var DEFAULT_ALUMNIUM_SERVER = "alumnium";
|
|
905
|
+
var ALUMNIUM_START_TOOL = "start";
|
|
906
|
+
function alumniumMaskerProps(props, server = DEFAULT_ALUMNIUM_SERVER) {
|
|
907
|
+
return {
|
|
908
|
+
...props,
|
|
909
|
+
registerResponsePaths: {
|
|
910
|
+
...alumniumRegisterResponsePaths(server),
|
|
911
|
+
...props.registerResponsePaths,
|
|
912
|
+
},
|
|
913
|
+
wholeValueOnlyTools: [`${server}/*`, ...(props.wholeValueOnlyTools ?? [])],
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
function alumniumRegisterResponsePaths(server) {
|
|
917
|
+
return {
|
|
918
|
+
[`${server}/*`]: [],
|
|
919
|
+
[`${server}/${ALUMNIUM_START_TOOL}`]: ["id"],
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// test/src/player/Player.ts
|
|
924
|
+
var { logger: logger7 } = Telemetry7.get(
|
|
925
|
+
"packages/typescript/test/src/player/Player.ts",
|
|
926
|
+
);
|
|
927
|
+
var START_TOOL = "start";
|
|
928
|
+
var STOP_TOOL = "stop";
|
|
929
|
+
var CHECK_TOOL = "check";
|
|
930
|
+
var NO_CACHE_META = { "alumnium.ai/cache": false };
|
|
931
|
+
var NO_CHANGE_ANALYSIS_META = { "alumnium.ai/change-analysis": false };
|
|
932
|
+
var DEFAULT_SKIP_TOOLS = ["ToolSearch", "TodoWrite"];
|
|
933
|
+
var DriverOutput = z3.object({ id: z3.string() });
|
|
934
|
+
var CheckOutput = z3.object({ result: z3.enum(["success", "failure"]) });
|
|
935
|
+
|
|
936
|
+
class Player {
|
|
937
|
+
#options;
|
|
938
|
+
#maskerProps;
|
|
939
|
+
constructor(props) {
|
|
940
|
+
const {
|
|
941
|
+
executor,
|
|
942
|
+
alumniumServer = DEFAULT_ALUMNIUM_SERVER,
|
|
943
|
+
skipTools = DEFAULT_SKIP_TOOLS,
|
|
944
|
+
checkConfirmations,
|
|
945
|
+
observer = {},
|
|
946
|
+
...maskerProps
|
|
947
|
+
} = props;
|
|
948
|
+
this.#options = {
|
|
949
|
+
executor,
|
|
950
|
+
server: alumniumServer,
|
|
951
|
+
skipTools: new Set(skipTools),
|
|
952
|
+
checkConfirmations,
|
|
953
|
+
observer,
|
|
954
|
+
};
|
|
955
|
+
this.#maskerProps = alumniumMaskerProps(maskerProps, alumniumServer);
|
|
956
|
+
}
|
|
957
|
+
async play(recording) {
|
|
958
|
+
const run = new PlayerRun(this.#options, new Masker(this.#maskerProps));
|
|
959
|
+
try {
|
|
960
|
+
return await run.play(recording);
|
|
961
|
+
} finally {
|
|
962
|
+
await run.stopDrivers();
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
class Diverged extends Error {}
|
|
968
|
+
|
|
969
|
+
class PlayerRun {
|
|
970
|
+
#options;
|
|
971
|
+
#masker;
|
|
972
|
+
#driverIds = new Set();
|
|
973
|
+
constructor(options, masker) {
|
|
974
|
+
this.#options = options;
|
|
975
|
+
this.#masker = masker;
|
|
976
|
+
}
|
|
977
|
+
get #observer() {
|
|
978
|
+
return this.#options.observer;
|
|
979
|
+
}
|
|
980
|
+
async play(recording) {
|
|
981
|
+
const stepsCount = Recording.executableStepsCount(recording);
|
|
982
|
+
let stepNumber = 0;
|
|
983
|
+
for (const [stepIndex, step] of recording.steps.entries()) {
|
|
984
|
+
if (step.kind === "narration") {
|
|
985
|
+
this.#observer.onNarration?.(step);
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
logger7.info(`Replaying step ${++stepNumber}/${stepsCount}`);
|
|
989
|
+
try {
|
|
990
|
+
await this.#playToolStep(step);
|
|
991
|
+
} catch (error) {
|
|
992
|
+
const message = error instanceof Diverged ? error.message : `${error}`;
|
|
993
|
+
logger7.error(`Replay diverged at step ${stepNumber}: ${message}`);
|
|
994
|
+
return { status: "diverged", error: message, stepIndex };
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
logger7.info(`Replayed all ${stepsCount} steps`);
|
|
998
|
+
return { status: "replayed", verdict: recording.verdict };
|
|
999
|
+
}
|
|
1000
|
+
async stopDrivers() {
|
|
1001
|
+
for (const id of this.#driverIds) {
|
|
1002
|
+
try {
|
|
1003
|
+
logger7.info(`Stopping driver ${id} the replay did not stop`);
|
|
1004
|
+
await this.#options.executor.execute({
|
|
1005
|
+
server: this.#options.server,
|
|
1006
|
+
tool: STOP_TOOL,
|
|
1007
|
+
input: { id, save_cache: false },
|
|
1008
|
+
});
|
|
1009
|
+
} catch (error) {
|
|
1010
|
+
logger7.warn(`Failed to stop driver ${id}: {error}`, { error });
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
this.#driverIds.clear();
|
|
1014
|
+
}
|
|
1015
|
+
async #playToolStep(step) {
|
|
1016
|
+
const { call: recordedCall, result: recordedResult } = step;
|
|
1017
|
+
const { id, ...callProps } = recordedCall;
|
|
1018
|
+
const toolKey = Recording.toolKey(recordedCall);
|
|
1019
|
+
if (recordedResult.isError)
|
|
1020
|
+
return this.#skip(recordedCall, "it failed when recorded");
|
|
1021
|
+
if (!this.#options.executor.handles(callProps)) {
|
|
1022
|
+
if (this.#options.skipTools.has(toolKey))
|
|
1023
|
+
return this.#skip(recordedCall, "it only exists inside the agent");
|
|
1024
|
+
throw new Diverged(`No executor for tool '${toolKey}'`);
|
|
1025
|
+
}
|
|
1026
|
+
const call = {
|
|
1027
|
+
...callProps,
|
|
1028
|
+
input: this.#masker.unmaskInput(toolKey, recordedCall.input),
|
|
1029
|
+
};
|
|
1030
|
+
const unresolved = Masker.findUnresolvedMasks(call.input);
|
|
1031
|
+
if (unresolved.length)
|
|
1032
|
+
throw new Diverged(
|
|
1033
|
+
`Tool '${toolKey}' input has unresolved masks: ${unresolved.join(", ")}`,
|
|
1034
|
+
);
|
|
1035
|
+
const result = await this.#execute(
|
|
1036
|
+
call,
|
|
1037
|
+
call.server === this.#options.server && call.tool === START_TOOL
|
|
1038
|
+
? { meta: NO_CHANGE_ANALYSIS_META }
|
|
1039
|
+
: undefined,
|
|
1040
|
+
);
|
|
1041
|
+
if (result.isError)
|
|
1042
|
+
throw new Diverged(
|
|
1043
|
+
`Tool '${toolKey}' failed: ${contentTexts(result.content).join(`
|
|
1044
|
+
`)}`,
|
|
1045
|
+
);
|
|
1046
|
+
this.#masker.registerOutput(id, result.content, toolKey);
|
|
1047
|
+
if (call.server !== this.#options.server) return;
|
|
1048
|
+
switch (call.tool) {
|
|
1049
|
+
case START_TOOL: {
|
|
1050
|
+
const driverId = this.#parseOutput(result.content, DriverOutput)?.id;
|
|
1051
|
+
if (driverId) this.#driverIds.add(driverId);
|
|
1052
|
+
break;
|
|
1053
|
+
}
|
|
1054
|
+
case STOP_TOOL: {
|
|
1055
|
+
const driverId = DriverOutput.safeParse(call.input).data?.id;
|
|
1056
|
+
if (driverId) this.#driverIds.delete(driverId);
|
|
1057
|
+
break;
|
|
1058
|
+
}
|
|
1059
|
+
case CHECK_TOOL:
|
|
1060
|
+
await this.#compareCheck(call, recordedResult, result);
|
|
1061
|
+
break;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
async #execute(call, options) {
|
|
1065
|
+
this.#observer.onToolCall?.(call);
|
|
1066
|
+
const result = await this.#options.executor.execute(call, options);
|
|
1067
|
+
this.#observer.onToolResult?.(call, result);
|
|
1068
|
+
return result;
|
|
1069
|
+
}
|
|
1070
|
+
#skip(call, reason) {
|
|
1071
|
+
logger7.info(`Skipping tool '${Recording.toolKey(call)}', ${reason}`);
|
|
1072
|
+
this.#observer.onToolSkipped?.(call, reason);
|
|
1073
|
+
}
|
|
1074
|
+
async #compareCheck(call, recorded, replayed) {
|
|
1075
|
+
const confirmations =
|
|
1076
|
+
this.#options.checkConfirmations ?? Env2.ALUMNIUM_CHECK_CONFIRMATIONS;
|
|
1077
|
+
const expected = this.#checkVerdict(recorded);
|
|
1078
|
+
let actual = this.#checkVerdict(replayed);
|
|
1079
|
+
let outcome;
|
|
1080
|
+
if (!expected || !actual) {
|
|
1081
|
+
throw new Diverged(
|
|
1082
|
+
`Cannot read '${Recording.toolKey(call)}' verdict to compare`,
|
|
1083
|
+
);
|
|
1084
|
+
} else if (expected === actual) {
|
|
1085
|
+
outcome = "agreed";
|
|
1086
|
+
} else if (expected === "failure") {
|
|
1087
|
+
outcome = "improved";
|
|
1088
|
+
} else {
|
|
1089
|
+
let attempt = 0;
|
|
1090
|
+
while (actual !== expected && attempt++ < confirmations) {
|
|
1091
|
+
logger7.info(`Re-asking check ${attempt}/${confirmations}`);
|
|
1092
|
+
const result = await this.#execute(call, { meta: NO_CACHE_META });
|
|
1093
|
+
actual = result.isError ? null : this.#checkVerdict(result);
|
|
1094
|
+
}
|
|
1095
|
+
outcome = actual === expected ? "unstable" : "disagreed";
|
|
1096
|
+
}
|
|
1097
|
+
this.#observer.onCheck?.(call, outcome);
|
|
1098
|
+
if (outcome === "disagreed")
|
|
1099
|
+
throw new Diverged(
|
|
1100
|
+
`Check '${call.input["statement"]}' failed where it passed when recorded` +
|
|
1101
|
+
(confirmations
|
|
1102
|
+
? `, re-asked ${confirmations} times with the cache bypassed`
|
|
1103
|
+
: ""),
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
#checkVerdict(result) {
|
|
1107
|
+
return this.#parseOutput(result.content, CheckOutput)?.result ?? null;
|
|
1108
|
+
}
|
|
1109
|
+
#parseOutput(content, schema) {
|
|
1110
|
+
for (const text of contentTexts(content)) {
|
|
1111
|
+
const parsed = schema.safeParse(parseJson(text));
|
|
1112
|
+
if (parsed.success) return parsed.data;
|
|
1113
|
+
}
|
|
1114
|
+
return null;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
// test/src/player/ToolExecutor.ts
|
|
1118
|
+
function composeExecutors(...executors) {
|
|
1119
|
+
const find = (call) => executors.find((executor) => executor.handles(call));
|
|
1120
|
+
return {
|
|
1121
|
+
handles: (call) => Boolean(find(call)),
|
|
1122
|
+
async execute(call, options) {
|
|
1123
|
+
const executor = find(call);
|
|
1124
|
+
if (!executor)
|
|
1125
|
+
throw new Error(
|
|
1126
|
+
`No executor handles tool '${Recording.toolKey(call)}'`,
|
|
1127
|
+
);
|
|
1128
|
+
return executor.execute(call, options);
|
|
1129
|
+
},
|
|
1130
|
+
async close() {
|
|
1131
|
+
await Promise.all(
|
|
1132
|
+
executors.map(async (executor) => await executor.close?.()),
|
|
1133
|
+
);
|
|
1134
|
+
},
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
723
1137
|
// test/src/Recorder.ts
|
|
724
1138
|
import {
|
|
725
|
-
|
|
726
|
-
Telemetry as
|
|
1139
|
+
ALUMNIUM_CORE_VERSION as ALUMNIUM_CORE_VERSION2,
|
|
1140
|
+
Telemetry as Telemetry8,
|
|
727
1141
|
} from "@alumnium/core/internal";
|
|
728
1142
|
|
|
729
1143
|
// test/src/RecordingLeakError.ts
|
|
@@ -740,11 +1154,9 @@ class RecordingLeakError extends Error {
|
|
|
740
1154
|
}
|
|
741
1155
|
|
|
742
1156
|
// test/src/Recorder.ts
|
|
743
|
-
var { logger:
|
|
1157
|
+
var { logger: logger8 } = Telemetry8.get(
|
|
744
1158
|
"packages/typescript/test/src/Recorder.ts",
|
|
745
1159
|
);
|
|
746
|
-
var DEFAULT_ALUMNIUM_SERVER = "alumnium";
|
|
747
|
-
var ALUMNIUM_START_TOOL = "start";
|
|
748
1160
|
|
|
749
1161
|
class Recorder {
|
|
750
1162
|
#text;
|
|
@@ -759,24 +1171,13 @@ class Recorder {
|
|
|
759
1171
|
this.#version = version ?? Recording.DEFAULT_VERSION;
|
|
760
1172
|
this.#text = text;
|
|
761
1173
|
this.#agent = agent;
|
|
762
|
-
|
|
763
|
-
this.#masker = new Masker({
|
|
764
|
-
...maskerProps,
|
|
765
|
-
registerResponsePaths: {
|
|
766
|
-
...this.#alumniumRegisterResponsePaths(server),
|
|
767
|
-
...maskerProps.registerResponsePaths,
|
|
768
|
-
},
|
|
769
|
-
wholeValueOnlyTools: [
|
|
770
|
-
`${server}/*`,
|
|
771
|
-
...(maskerProps.wholeValueOnlyTools ?? []),
|
|
772
|
-
],
|
|
773
|
-
});
|
|
1174
|
+
this.#masker = new Masker(alumniumMaskerProps(maskerProps, alumniumServer));
|
|
774
1175
|
}
|
|
775
1176
|
onToolUse(use) {
|
|
776
1177
|
this.#assertNotFinished();
|
|
777
1178
|
const { id, server, tool, input } = use;
|
|
778
1179
|
const toolKey = Recording.toolKey({ server, tool });
|
|
779
|
-
|
|
1180
|
+
logger8.debug(`Recording tool use '${toolKey}' ({id})`, { id });
|
|
780
1181
|
this.#pendingCalls.set(id, {
|
|
781
1182
|
id,
|
|
782
1183
|
...(server ? { server } : {}),
|
|
@@ -790,11 +1191,11 @@ class Recorder {
|
|
|
790
1191
|
const isError = Boolean(toolResult.isError);
|
|
791
1192
|
const call = this.#pendingCalls.get(id);
|
|
792
1193
|
if (!call) {
|
|
793
|
-
|
|
1194
|
+
logger8.debug(`No recorded tool use for result '${id}', ignoring it`);
|
|
794
1195
|
return;
|
|
795
1196
|
}
|
|
796
1197
|
this.#pendingCalls.delete(id);
|
|
797
|
-
|
|
1198
|
+
logger8.debug(`Recording '${call.tool}' tool result`);
|
|
798
1199
|
this.#masker.registerOutput(id, content, Recording.toolKey(call), {
|
|
799
1200
|
secretsOnly: isError,
|
|
800
1201
|
});
|
|
@@ -816,7 +1217,7 @@ class Recorder {
|
|
|
816
1217
|
finish(verdict) {
|
|
817
1218
|
this.#assertNotFinished();
|
|
818
1219
|
if (this.#pendingCalls.size)
|
|
819
|
-
|
|
1220
|
+
logger8.warn(
|
|
820
1221
|
`Dropping ${this.#pendingCalls.size} tool calls with no result: ${[
|
|
821
1222
|
...this.#pendingCalls.values(),
|
|
822
1223
|
]
|
|
@@ -829,7 +1230,7 @@ class Recorder {
|
|
|
829
1230
|
version: this.#version,
|
|
830
1231
|
text: this.#text,
|
|
831
1232
|
agent: this.#agent,
|
|
832
|
-
alumnium: { version:
|
|
1233
|
+
alumnium: { version: ALUMNIUM_CORE_VERSION2 },
|
|
833
1234
|
hostVars: [],
|
|
834
1235
|
steps: this.#steps.map((step) => this.#maskStep(step)),
|
|
835
1236
|
verdict: {
|
|
@@ -864,17 +1265,21 @@ class Recorder {
|
|
|
864
1265
|
};
|
|
865
1266
|
}
|
|
866
1267
|
}
|
|
867
|
-
#alumniumRegisterResponsePaths(server = DEFAULT_ALUMNIUM_SERVER) {
|
|
868
|
-
return {
|
|
869
|
-
[`${server}/*`]: [],
|
|
870
|
-
[`${server}/${ALUMNIUM_START_TOOL}`]: ["id"],
|
|
871
|
-
};
|
|
872
|
-
}
|
|
873
1268
|
#assertNotFinished() {
|
|
874
1269
|
if (this.#recording) throw new Error("Recording is already finished");
|
|
875
1270
|
}
|
|
876
1271
|
}
|
|
877
|
-
export {
|
|
1272
|
+
export {
|
|
1273
|
+
BashExecutor,
|
|
1274
|
+
FileTestCache,
|
|
1275
|
+
Masker,
|
|
1276
|
+
McpExecutor,
|
|
1277
|
+
Player,
|
|
1278
|
+
Recorder,
|
|
1279
|
+
Recording,
|
|
1280
|
+
RecordingLeakError,
|
|
1281
|
+
composeExecutors,
|
|
1282
|
+
};
|
|
878
1283
|
|
|
879
|
-
//# debugId=
|
|
1284
|
+
//# debugId=88B58893F83F47D164756E2164756E21
|
|
880
1285
|
//# sourceMappingURL=index.js.map
|