@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.
@@ -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;