@tormentalabs/claude-code-wire-compat 0.6.0 → 0.7.0-rc.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.
@@ -0,0 +1,226 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+ import type { TextViolation } from "./unicode.js";
4
+
5
+ /*
6
+ * Safe validation-failure diagnostics (decision P1.T1, D4).
7
+ *
8
+ * Every value produced here is safe by construction: reasons come from a
9
+ * closed set, path segments from a closed vocabulary of schema-defined keys
10
+ * plus numeric indices and the `*` placeholder for user-controlled keys, and
11
+ * reported code units only ever come from control or surrogate ranges. No
12
+ * caller text, key name or excerpt can leak through these fields.
13
+ */
14
+
15
+ export const VIOLATION_REASONS: ReadonlySet<string> = new Set([
16
+ "lone-surrogate",
17
+ "control-char",
18
+ ]);
19
+
20
+ /**
21
+ * Schema-defined keys allowed verbatim in a violation path. Any other object
22
+ * key (tool input names, `input_schema` property names, arbitrary metadata
23
+ * members) is user-controlled text and is reported as `*`.
24
+ */
25
+ const PATH_KEY_VOCABULARY: ReadonlySet<string> = new Set([
26
+ "$schema",
27
+ "accept",
28
+ "additionalBetas",
29
+ "additionalProperties",
30
+ "anthropicAdditionalProtection",
31
+ "app",
32
+ "arch",
33
+ "accountUuid",
34
+ "body",
35
+ "cache_control",
36
+ "citations",
37
+ "clientApp",
38
+ "clientRequestId",
39
+ "claudeRemoteContainerId",
40
+ "claudeRemoteSessionId",
41
+ "content",
42
+ "context",
43
+ "crypto",
44
+ "data",
45
+ "default",
46
+ "defer_loading",
47
+ "description",
48
+ "deviceId",
49
+ "document",
50
+ "effort",
51
+ "enum",
52
+ "evidence",
53
+ "examples",
54
+ "extraHeaderPolicy",
55
+ "extraHeaders",
56
+ "headers",
57
+ "id",
58
+ "image",
59
+ "input",
60
+ "input_schema",
61
+ "input_examples",
62
+ "items",
63
+ "max_tokens",
64
+ "maxTokens",
65
+ "media_type",
66
+ "messages",
67
+ "metadata",
68
+ "metadataOverrides",
69
+ "method",
70
+ "model",
71
+ "name",
72
+ "os",
73
+ "output_config",
74
+ "profileOverride",
75
+ "properties",
76
+ "redacted_thinking",
77
+ "required",
78
+ "role",
79
+ "runtime",
80
+ "runtimeVersion",
81
+ "search_results",
82
+ "sessionId",
83
+ "signature",
84
+ "source",
85
+ "stainlessHelper",
86
+ "stainlessRetryCount",
87
+ "stop_sequences",
88
+ "stream",
89
+ "strict",
90
+ "system",
91
+ "text",
92
+ "thinking",
93
+ "timing",
94
+ "title",
95
+ "tool_name",
96
+ "tool_reference",
97
+ "tool_result",
98
+ "tool_use",
99
+ "tool_use_id",
100
+ "tools",
101
+ "type",
102
+ "url",
103
+ "user_id",
104
+ "userId",
105
+ "userIdFields",
106
+ ]);
107
+
108
+ const MAX_PATH_SEGMENTS = 16;
109
+ const MAX_PATH_LENGTH = 256;
110
+ const TRUNCATION_MARKER = "...";
111
+ const OPAQUE_SUBTREES: ReadonlySet<string> = new Set([
112
+ "headers",
113
+ "input",
114
+ "input_schema",
115
+ "input_examples",
116
+ "metadata",
117
+ "metadataOverrides",
118
+ "userIdFields",
119
+ "extraHeaders",
120
+ "data",
121
+ "default",
122
+ "examples",
123
+ ]);
124
+
125
+ export type ViolationPathSegment = string | number;
126
+
127
+ function mapSegment(segment: ViolationPathSegment, opaque: boolean): string {
128
+ if (typeof segment === "number") {
129
+ return Number.isSafeInteger(segment) &&
130
+ !Object.is(segment, -0) &&
131
+ segment >= 0 &&
132
+ segment <= 999_999
133
+ ? String(segment)
134
+ : "*";
135
+ }
136
+ return !opaque && PATH_KEY_VOCABULARY.has(segment) ? segment : "*";
137
+ }
138
+
139
+ /**
140
+ * Renders a pointer-like path (`/messages/0/content`) from walk segments.
141
+ * At most 16 segments, then a trailing `/...`; at most 256 characters.
142
+ */
143
+ export function formatViolationPath(
144
+ segments: readonly ViolationPathSegment[],
145
+ ): string {
146
+ let path = "";
147
+ let opaque = false;
148
+ for (let index = 0; index < segments.length; index += 1) {
149
+ const segment = mapSegment(segments[index] ?? "*", opaque);
150
+ const tailLength = index < segments.length - 1 ? 4 : 0;
151
+ if (
152
+ index >= MAX_PATH_SEGMENTS ||
153
+ path.length + 1 + segment.length + tailLength > MAX_PATH_LENGTH
154
+ ) {
155
+ return `${path}/${TRUNCATION_MARKER}`;
156
+ }
157
+ path += `/${segment}`;
158
+ opaque ||= segment === "*" || OPAQUE_SUBTREES.has(segment);
159
+ }
160
+ return path || "/";
161
+ }
162
+
163
+ export function isValidViolationReason(value: string): boolean {
164
+ return VIOLATION_REASONS.has(value);
165
+ }
166
+
167
+ const PATH_INDEX_SEGMENT = /^(?:0|[1-9]\d{0,5})$/u;
168
+
169
+ /**
170
+ * Revalidates a formatted violation path for the redaction allowlist. Every
171
+ * segment must be a vocabulary key, a decimal index of at most 6 digits, `*`,
172
+ * or the single trailing truncation marker; the whole string is capped.
173
+ */
174
+ export function isValidViolationPath(value: string): boolean {
175
+ if (value.length === 0 || value.length > MAX_PATH_LENGTH) return false;
176
+ if (!value.startsWith("/")) return false;
177
+ if (value === "/") return true;
178
+ const segments = value.slice(1).split("/");
179
+ if (segments.length > MAX_PATH_SEGMENTS + 1) return false;
180
+ let opaque = false;
181
+ for (let index = 0; index < segments.length; index += 1) {
182
+ const segment = segments[index];
183
+ if (segment === undefined) return false;
184
+ if (segment === TRUNCATION_MARKER) return index === segments.length - 1;
185
+ if (index >= MAX_PATH_SEGMENTS) return false;
186
+ if (segment === "*") {
187
+ opaque = true;
188
+ continue;
189
+ }
190
+ if (PATH_INDEX_SEGMENT.test(segment)) continue;
191
+ if (opaque || !PATH_KEY_VOCABULARY.has(segment)) return false;
192
+ opaque ||= OPAQUE_SUBTREES.has(segment);
193
+ }
194
+ return true;
195
+ }
196
+
197
+ /** Numeric range guard for `violationCodeUnit`: controls or surrogates only. */
198
+ export function isValidViolationCodeUnit(value: number): boolean {
199
+ if (!Number.isInteger(value) || Object.is(value, -0) || value < 0)
200
+ return false;
201
+ return (
202
+ value <= 0x1f ||
203
+ (value >= 0x7f && value <= 0x9f) ||
204
+ (value >= 0xd800 && value <= 0xdfff)
205
+ );
206
+ }
207
+
208
+ /**
209
+ * Builds the safeDetails record for one text violation found at `path`.
210
+ * `inKey` distinguishes an offending object key from an offending value.
211
+ */
212
+ export function violationDetails(
213
+ violation: TextViolation,
214
+ path: readonly ViolationPathSegment[],
215
+ textLength: number,
216
+ inKey: boolean,
217
+ ): Readonly<Record<string, string | number | boolean>> {
218
+ return {
219
+ violationReason: violation.reason,
220
+ violationPath: formatViolationPath(path),
221
+ violationOffset: violation.offset,
222
+ violationCodeUnit: violation.codeUnit,
223
+ violationTextLength: textLength,
224
+ violationInKey: inKey,
225
+ };
226
+ }