@alumnium/test 0.0.0-stage → 0.24.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/LICENSE.md +21 -0
- package/README.md +2 -2
- package/package.json +37 -4
- package/src/FileTestCache.d.ts +26 -0
- package/src/Recorder.d.ts +85 -0
- package/src/Recording.d.ts +288 -0
- package/src/RecordingLeakError.d.ts +4 -0
- package/src/TestCache.d.ts +38 -0
- package/src/index.cjs +961 -0
- package/src/index.cjs.map +20 -0
- package/src/index.d.ts +6 -0
- package/src/index.js +880 -0
- package/src/index.js.map +20 -0
- package/src/masker/HostVarMasker.d.ts +27 -0
- package/src/masker/Masker.d.ts +163 -0
- package/src/masker/ResponsePaths.d.ts +35 -0
- package/src/masker/SecretMasker.d.ts +58 -0
- package/src/masker/SubstringMasker.d.ts +41 -0
- package/src/masker/ToolCallMasker.d.ts +177 -0
- package/src/masker/maskerUtils.d.ts +83 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import type { Recording } from "../Recording.ts";
|
|
2
|
+
export declare namespace ToolCallMasker {
|
|
3
|
+
interface Props {
|
|
4
|
+
/** @see Masker.Props.verbatimTools */
|
|
5
|
+
verbatimTools?: string[] | undefined;
|
|
6
|
+
/** @see Masker.Props.wholeValueOnlyTools */
|
|
7
|
+
wholeValueOnlyTools?: string[] | undefined;
|
|
8
|
+
/** @see `Masker.Props.maskResponsePaths`. */
|
|
9
|
+
maskResponsePaths?: Record<string, string[]> | undefined;
|
|
10
|
+
/** @see `Masker.Props.registerResponsePaths`. */
|
|
11
|
+
registerResponsePaths?: Record<string, string[]> | undefined;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Which strings a mask can take the place of.
|
|
15
|
+
* - whole: a tool call value in full (e.g. `{"id": "a1"}`).
|
|
16
|
+
* - embedded: a quote-delimited value inside a larger string (e.g. `echo '{"id": "a1"}'`).
|
|
17
|
+
*/
|
|
18
|
+
type Scope = "whole" | "embedded";
|
|
19
|
+
interface Leaf {
|
|
20
|
+
path: string[];
|
|
21
|
+
value: string;
|
|
22
|
+
}
|
|
23
|
+
interface RegisterOptions {
|
|
24
|
+
/** Registers only the leaves at `maskResponsePaths`, e.g. of a failed call. */
|
|
25
|
+
secretsOnly?: boolean | undefined;
|
|
26
|
+
}
|
|
27
|
+
interface Registered {
|
|
28
|
+
mask: string;
|
|
29
|
+
value: string;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Masks values tool calls produced, and substitutes the values the replay's
|
|
34
|
+
* fresh calls produce. Masks are derived from the JSON path of each output
|
|
35
|
+
* leaf, so the same leaf yields the same mask in both phases:
|
|
36
|
+
*
|
|
37
|
+
* - `<tool-call.id.path>` - an ordinary value, masked only where a later tool
|
|
38
|
+
* input uses it as a whole value (or a quoted one, in the `embedded` scope),
|
|
39
|
+
* since it can be short or common;
|
|
40
|
+
* - `<secret.id.path>` - a value at one of the tool's `maskResponsePaths`,
|
|
41
|
+
* masked like an ordinary one and also wherever else it occurs, results and
|
|
42
|
+
* narration included, since it must never be stored in clear (see
|
|
43
|
+
* `SecretMasker`).
|
|
44
|
+
*/
|
|
45
|
+
export declare class ToolCallMasker {
|
|
46
|
+
#private;
|
|
47
|
+
constructor(props?: ToolCallMasker.Props);
|
|
48
|
+
/**
|
|
49
|
+
* Tells whether tool call values are masked in a tool's input at all.
|
|
50
|
+
*
|
|
51
|
+
* Every tool but a verbatim one qualifies, including one replay cannot
|
|
52
|
+
* execute: the point of masking such an input is that the value a run
|
|
53
|
+
* produced does not end up persisted in the recording, not that it gets
|
|
54
|
+
* substituted again.
|
|
55
|
+
*
|
|
56
|
+
* @param toolKey - Tool key, see `Recording.toolKey`.
|
|
57
|
+
* @returns `false` when the tool's input is prose that must stay verbatim.
|
|
58
|
+
*/
|
|
59
|
+
masksInputOf(toolKey: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Scope tool call values are masked in for a tool's input: `whole` for one
|
|
62
|
+
* of `wholeValueOnlyTools`, `embedded` for any other.
|
|
63
|
+
*
|
|
64
|
+
* @param toolKey - Tool key, see `Recording.toolKey`.
|
|
65
|
+
*/
|
|
66
|
+
scopeOf(toolKey: string): ToolCallMasker.Scope;
|
|
67
|
+
/**
|
|
68
|
+
* Masks a tool's input in the tool's scope (see `scopeOf`), or only its
|
|
69
|
+
* secrets when the tool's input must stay verbatim.
|
|
70
|
+
*
|
|
71
|
+
* @param toolKey - Tool key, see `Recording.toolKey`.
|
|
72
|
+
* @param input - Tool input, masked in place.
|
|
73
|
+
* @returns The same input.
|
|
74
|
+
*/
|
|
75
|
+
maskInput(toolKey: string, input: Recording.Input): Recording.Input;
|
|
76
|
+
/**
|
|
77
|
+
* Substitutes fresh values back into a tool's input, in the tool's scope.
|
|
78
|
+
*
|
|
79
|
+
* @param toolKey - Tool key, see `Recording.toolKey`.
|
|
80
|
+
* @param input - Masked tool input, unmasked in place.
|
|
81
|
+
* @returns The same input.
|
|
82
|
+
*/
|
|
83
|
+
unmaskInput(toolKey: string, input: Recording.Input): Recording.Input;
|
|
84
|
+
/**
|
|
85
|
+
* Finds masks unmasking could not resolve: a `<tool-call...>` one that
|
|
86
|
+
* takes up a whole value, a `<secret...>` one anywhere.
|
|
87
|
+
*
|
|
88
|
+
* An ordinary mask inside a quoted token does not count. An unresolved
|
|
89
|
+
* one there just replays the command as recorded rather than failing the
|
|
90
|
+
* replay, whereas a secret replayed as its mask is never right.
|
|
91
|
+
*
|
|
92
|
+
* @param input - Unmasked tool input.
|
|
93
|
+
* @returns Masks, in order of appearance.
|
|
94
|
+
*/
|
|
95
|
+
static findUnresolved(input: Recording.Input): string[];
|
|
96
|
+
/**
|
|
97
|
+
* Finds secrets that survived masking anywhere in a value, see
|
|
98
|
+
* `SecretMasker.findLeaks`.
|
|
99
|
+
*
|
|
100
|
+
* @param value - Value to scan.
|
|
101
|
+
* @returns Masks of the leaked secrets, never the secrets themselves.
|
|
102
|
+
*/
|
|
103
|
+
findLeaks(value: unknown): string[];
|
|
104
|
+
/**
|
|
105
|
+
* Registers the values of a tool call result. Only JSON output is
|
|
106
|
+
* considered, so the same leaf yields the same mask in both phases even
|
|
107
|
+
* when a fresh output reorders its keys or adds new ones.
|
|
108
|
+
*
|
|
109
|
+
* @param callId - Id of the tool call, as the host agent assigned it.
|
|
110
|
+
* @param content - Tool call result content.
|
|
111
|
+
* @param toolKey - Tool key (see `Recording.toolKey`) for the response paths.
|
|
112
|
+
* @returns Leaves registered, with their masks.
|
|
113
|
+
*/
|
|
114
|
+
registerContent(
|
|
115
|
+
callId: string,
|
|
116
|
+
content: Recording.Content,
|
|
117
|
+
toolKey?: string,
|
|
118
|
+
options?: ToolCallMasker.RegisterOptions,
|
|
119
|
+
): ToolCallMasker.Registered[];
|
|
120
|
+
/**
|
|
121
|
+
* Registers the scalar leaves of a tool call's JSON output, so that they
|
|
122
|
+
* can be masked in the tool inputs that follow it (recording) or
|
|
123
|
+
* substituted back into them (replay).
|
|
124
|
+
*
|
|
125
|
+
* A tool with `registerResponsePaths` registers only the leaves they cover,
|
|
126
|
+
* any other tool every leaf. A leaf at one of the tool's
|
|
127
|
+
* `maskResponsePaths` becomes a secret, and is registered regardless, since
|
|
128
|
+
* it must be kept out of the recording either way.
|
|
129
|
+
*
|
|
130
|
+
* @param callId - Id of the tool call, as the host agent assigned it.
|
|
131
|
+
* @param json - Parsed JSON output of the call.
|
|
132
|
+
* @param toolKey - Tool key (see `Recording.toolKey`) for the response paths.
|
|
133
|
+
* @returns Leaves registered, with their masks.
|
|
134
|
+
*/
|
|
135
|
+
registerOutput(
|
|
136
|
+
callId: string,
|
|
137
|
+
json: object,
|
|
138
|
+
toolKey?: string,
|
|
139
|
+
options?: ToolCallMasker.RegisterOptions,
|
|
140
|
+
): ToolCallMasker.Registered[];
|
|
141
|
+
/**
|
|
142
|
+
* Replaces values produced by tool calls with masks, so that replay can
|
|
143
|
+
* substitute freshly produced ones.
|
|
144
|
+
*
|
|
145
|
+
* In the `whole` scope only a leaf that equals a tool call value in full is
|
|
146
|
+
* replaced. A value quoted inside free-form prose is left alone: there is no
|
|
147
|
+
* way to tell a value apart from the prose around it without guessing, and
|
|
148
|
+
* every Alumnium tool that takes prose takes such values through `params`.
|
|
149
|
+
*
|
|
150
|
+
* Secrets are then masked wherever else they occur, see `maskSecrets`.
|
|
151
|
+
*
|
|
152
|
+
* @param input - Tool input, masked in place.
|
|
153
|
+
* @param scope - Which strings a mask can take the place of.
|
|
154
|
+
* @returns The same input.
|
|
155
|
+
*/
|
|
156
|
+
mask(input: Recording.Input, scope: ToolCallMasker.Scope): Recording.Input;
|
|
157
|
+
/**
|
|
158
|
+
* Masks secrets wherever they occur in every string leaf of a value, e.g. a
|
|
159
|
+
* tool result, narration, or the input of a tool that must stay verbatim.
|
|
160
|
+
*
|
|
161
|
+
* @param value - Value to mask in place, or a string to mask.
|
|
162
|
+
* @returns The same value, or the masked string.
|
|
163
|
+
*/
|
|
164
|
+
maskSecrets<Type>(value: Type): Type;
|
|
165
|
+
/**
|
|
166
|
+
* Substitutes fresh tool call values back into a tool input, secrets
|
|
167
|
+
* wherever they occur.
|
|
168
|
+
*
|
|
169
|
+
* A mask with no registered value is left in place, so that
|
|
170
|
+
* `findUnresolved` can name the input it was needed for.
|
|
171
|
+
*
|
|
172
|
+
* @param input - Masked tool input, unmasked in place.
|
|
173
|
+
* @param scope - Which strings a mask can have taken the place of.
|
|
174
|
+
* @returns The same input.
|
|
175
|
+
*/
|
|
176
|
+
unmask(input: Recording.Input, scope: ToolCallMasker.Scope): Recording.Input;
|
|
177
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Recording } from "../Recording.ts";
|
|
2
|
+
/** Replacement for a string leaf, `undefined` to keep it. */
|
|
3
|
+
export type LeafFn = (value: string, path: string) => string | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* Visits every string leaf of a value, at any nesting depth, so that the
|
|
6
|
+
* values inside e.g. `do`'s `params` are covered too.
|
|
7
|
+
*
|
|
8
|
+
* Only string leaves are visited. Substituting a mask into a number or a
|
|
9
|
+
* boolean leaf would change its type and can break the tool input schema.
|
|
10
|
+
*
|
|
11
|
+
* @param container - Value to walk, only objects and arrays are descended into.
|
|
12
|
+
* @param mapLeaf - Called with each string leaf and its path, returning the
|
|
13
|
+
* replacement for it or `undefined` to leave it alone.
|
|
14
|
+
* @param path - Path of `container` itself, used for logging.
|
|
15
|
+
*/
|
|
16
|
+
export declare function eachStringLeaf(
|
|
17
|
+
container: unknown,
|
|
18
|
+
mapLeaf: LeafFn,
|
|
19
|
+
path?: string,
|
|
20
|
+
): void;
|
|
21
|
+
/**
|
|
22
|
+
* Finds every match of a global mask pattern in the string leaves of a value.
|
|
23
|
+
*
|
|
24
|
+
* @param value - Value to scan.
|
|
25
|
+
* @param pattern - Global mask pattern.
|
|
26
|
+
* @returns Matched masks, in order of appearance.
|
|
27
|
+
*/
|
|
28
|
+
export declare function findMasks(value: unknown, pattern: RegExp): string[];
|
|
29
|
+
/**
|
|
30
|
+
* Parses a tool input, cloning it so that masking never touches the caller's
|
|
31
|
+
* own object.
|
|
32
|
+
*
|
|
33
|
+
* @param value - Tool input.
|
|
34
|
+
* @returns Cloned tool input.
|
|
35
|
+
*/
|
|
36
|
+
export declare function parseInput(value: unknown): Recording.Input;
|
|
37
|
+
export declare function parseJson(text: string): unknown;
|
|
38
|
+
/**
|
|
39
|
+
* Parses a tool output as JSON, accepting only an object or an array.
|
|
40
|
+
*
|
|
41
|
+
* `JSON.parse` also accepts a bare scalar, so `echo 9` would otherwise
|
|
42
|
+
* register a value with no key to name it, inconsistent with `echo nine`,
|
|
43
|
+
* which is not JSON at all.
|
|
44
|
+
*
|
|
45
|
+
* @param text - Tool output text.
|
|
46
|
+
* @returns Parsed JSON structure, `null` when the output is not one.
|
|
47
|
+
*/
|
|
48
|
+
export declare function parseJsonStructure(text: string): object | null;
|
|
49
|
+
/** Text of a tool result: the string itself, or every text block. */
|
|
50
|
+
export declare function contentTexts(content: Recording.Content): string[];
|
|
51
|
+
/** Maps the text of a tool result, keeping every other block as it was. */
|
|
52
|
+
export declare function mapTexts(
|
|
53
|
+
content: Recording.Content,
|
|
54
|
+
map: (text: string) => string,
|
|
55
|
+
): Recording.Content;
|
|
56
|
+
/**
|
|
57
|
+
* Tells whether a tool key is one of a set, by its own key or its server's
|
|
58
|
+
* `<server>/*` key.
|
|
59
|
+
*
|
|
60
|
+
* @param keys - Tool keys, see `Recording.toolKey`.
|
|
61
|
+
* @param toolKey - Tool key to look up.
|
|
62
|
+
*/
|
|
63
|
+
export declare function hasToolKey(keys: Set<string>, toolKey: string): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Sanitizes a segment of a mask - a tool call id or a JSON path segment - to
|
|
66
|
+
* word characters, so that a mask stays matchable by its pattern.
|
|
67
|
+
*
|
|
68
|
+
* @param segment - Segment to sanitize.
|
|
69
|
+
*/
|
|
70
|
+
export declare function maskSegment(segment: string): string;
|
|
71
|
+
/**
|
|
72
|
+
* Maps the parts of a string outside any mask, of any family, leaving the
|
|
73
|
+
* masks themselves intact.
|
|
74
|
+
*
|
|
75
|
+
* @param value - String to map.
|
|
76
|
+
* @param map - Called with each part between masks.
|
|
77
|
+
*/
|
|
78
|
+
export declare function mapOutsideMasks(
|
|
79
|
+
value: string,
|
|
80
|
+
map: (text: string) => string,
|
|
81
|
+
): string;
|
|
82
|
+
/** Escapes a string for use as a literal in a `RegExp`. */
|
|
83
|
+
export declare function escapeRegExp(value: string): string;
|