@slates/provider 1.0.0-rc.2 → 1.0.0-rc.21

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.
Files changed (125) hide show
  1. package/dist/index.cjs +2885 -2
  2. package/dist/index.d.cts +1019 -0
  3. package/dist/index.d.ts +1019 -9
  4. package/dist/index.module.js +2775 -2
  5. package/package.json +9 -6
  6. package/src/action/action.ts +92 -4
  7. package/src/action/attachment.ts +87 -0
  8. package/src/action/builder.test.ts +136 -0
  9. package/src/action/builder.ts +113 -15
  10. package/src/action/directUpload.test.ts +70 -0
  11. package/src/action/directUpload.ts +105 -0
  12. package/src/action/getFileUrlTool.ts +41 -0
  13. package/src/action/index.ts +4 -2
  14. package/src/action/publicTool.ts +36 -0
  15. package/src/action/scopes.test.ts +76 -0
  16. package/src/action/scopes.ts +103 -0
  17. package/src/action/tool.ts +22 -6
  18. package/src/action/trigger.ts +19 -11
  19. package/src/adapter/adapter.ts +38 -0
  20. package/src/adapter/index.test.ts +322 -0
  21. package/src/adapter/index.ts +2 -0
  22. package/src/adapter/spec.ts +154 -0
  23. package/src/auth/auth.ts +3 -3
  24. package/src/auth/index.ts +2 -0
  25. package/src/auth/oauth.test.ts +83 -0
  26. package/src/auth/oauth.ts +150 -0
  27. package/src/auth/redact.test.ts +103 -0
  28. package/src/auth/redact.ts +111 -0
  29. package/src/auth/types.ts +72 -13
  30. package/src/axios/http.test.ts +83 -0
  31. package/src/axios/index.ts +116 -10
  32. package/src/axios/trace.test.ts +377 -0
  33. package/src/axios/trace.ts +803 -0
  34. package/src/config/config.ts +14 -3
  35. package/src/context/context.ts +264 -17
  36. package/src/context/hook.test.ts +46 -0
  37. package/src/context/hook.ts +15 -6
  38. package/src/context/index.ts +1 -0
  39. package/src/context/pQueue.ts +4 -0
  40. package/src/crypto/hmac.test.ts +40 -0
  41. package/src/crypto/hmac.ts +32 -0
  42. package/src/crypto/index.ts +1 -0
  43. package/src/docs.ts +22 -0
  44. package/src/error/api.test.ts +114 -0
  45. package/src/error/api.ts +236 -0
  46. package/src/error/axios.ts +220 -0
  47. package/src/error/base.test.ts +178 -0
  48. package/src/error/base.ts +108 -2
  49. package/src/error/declaration.ts +7 -2
  50. package/src/error/defaults.ts +50 -0
  51. package/src/error/index.ts +4 -0
  52. package/src/error/service.ts +170 -0
  53. package/src/error/types.ts +75 -0
  54. package/src/error/utils.ts +157 -0
  55. package/src/file/index.test.ts +36 -0
  56. package/src/file/index.ts +27 -0
  57. package/src/index.ts +8 -0
  58. package/src/logger/logger.test.ts +57 -0
  59. package/src/logger/logger.ts +72 -11
  60. package/src/module/resolveDefaultExport.test.ts +32 -0
  61. package/src/module/resolveDefaultExport.ts +14 -0
  62. package/src/object/index.test.ts +38 -0
  63. package/src/object/index.ts +12 -0
  64. package/src/specification/slate.ts +40 -4
  65. package/src/specification/specification.ts +10 -6
  66. package/src/webhook/graph.test.ts +37 -0
  67. package/src/webhook/graph.ts +22 -0
  68. package/src/webhook/index.ts +2 -0
  69. package/src/webhook/meta.test.ts +52 -0
  70. package/src/webhook/meta.ts +43 -0
  71. package/dist/action/action.d.ts +0 -98
  72. package/dist/action/action.d.ts.map +0 -1
  73. package/dist/action/builder.d.ts +0 -27
  74. package/dist/action/builder.d.ts.map +0 -1
  75. package/dist/action/index.d.ts +0 -5
  76. package/dist/action/index.d.ts.map +0 -1
  77. package/dist/action/tool.d.ts +0 -14
  78. package/dist/action/tool.d.ts.map +0 -1
  79. package/dist/action/trigger.d.ts +0 -32
  80. package/dist/action/trigger.d.ts.map +0 -1
  81. package/dist/auth/auth.d.ts +0 -30
  82. package/dist/auth/auth.d.ts.map +0 -1
  83. package/dist/auth/index.d.ts +0 -3
  84. package/dist/auth/index.d.ts.map +0 -1
  85. package/dist/auth/types.d.ts +0 -148
  86. package/dist/auth/types.d.ts.map +0 -1
  87. package/dist/axios/index.d.ts +0 -4
  88. package/dist/axios/index.d.ts.map +0 -1
  89. package/dist/config/config.d.ts +0 -26
  90. package/dist/config/config.d.ts.map +0 -1
  91. package/dist/config/index.d.ts +0 -2
  92. package/dist/config/index.d.ts.map +0 -1
  93. package/dist/context/context.d.ts +0 -20
  94. package/dist/context/context.d.ts.map +0 -1
  95. package/dist/context/hook.d.ts +0 -4
  96. package/dist/context/hook.d.ts.map +0 -1
  97. package/dist/context/index.d.ts +0 -2
  98. package/dist/context/index.d.ts.map +0 -1
  99. package/dist/error/base.d.ts +0 -5
  100. package/dist/error/base.d.ts.map +0 -1
  101. package/dist/error/declaration.d.ts +0 -6
  102. package/dist/error/declaration.d.ts.map +0 -1
  103. package/dist/error/index.d.ts +0 -3
  104. package/dist/error/index.d.ts.map +0 -1
  105. package/dist/index.cjs.map +0 -1
  106. package/dist/index.d.ts.map +0 -1
  107. package/dist/index.modern.js +0 -2
  108. package/dist/index.modern.js.map +0 -1
  109. package/dist/index.module.js.map +0 -1
  110. package/dist/index.umd.js +0 -2
  111. package/dist/index.umd.js.map +0 -1
  112. package/dist/logger/index.d.ts +0 -2
  113. package/dist/logger/index.d.ts.map +0 -1
  114. package/dist/logger/logger.d.ts +0 -20
  115. package/dist/logger/logger.d.ts.map +0 -1
  116. package/dist/specification/index.d.ts +0 -3
  117. package/dist/specification/index.d.ts.map +0 -1
  118. package/dist/specification/slate.d.ts +0 -15
  119. package/dist/specification/slate.d.ts.map +0 -1
  120. package/dist/specification/specification.d.ts +0 -30
  121. package/dist/specification/specification.d.ts.map +0 -1
  122. package/dist/specification/zero.d.ts +0 -13
  123. package/dist/specification/zero.d.ts.map +0 -1
  124. package/dist/tokens.d.ts +0 -34
  125. package/dist/tokens.d.ts.map +0 -1
@@ -0,0 +1,803 @@
1
+ import type { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
2
+ import { AuthConfigSecretRedactor } from '../auth/redact';
3
+ import type { SlatePublicContext } from '../context';
4
+
5
+ export interface SlateHttpTraceTextBody {
6
+ contentType?: string;
7
+ text: string;
8
+ truncated?: boolean;
9
+ }
10
+
11
+ export interface SlateHttpTrace {
12
+ startedAt: string;
13
+ durationMs: number;
14
+ request: {
15
+ method: string;
16
+ url: string;
17
+ headers?: Record<string, string>;
18
+ body?: SlateHttpTraceTextBody;
19
+ };
20
+ response?: {
21
+ status: number;
22
+ statusText?: string;
23
+ headers?: Record<string, string>;
24
+ body?: SlateHttpTraceTextBody;
25
+ };
26
+ error?: {
27
+ code?: string;
28
+ message: string;
29
+ };
30
+ }
31
+
32
+ interface SlateHttpTraceDraft {
33
+ context: SlatePublicContext<any>;
34
+ startedAt: string;
35
+ startedAtMs: number;
36
+ request: SlateHttpTrace['request'];
37
+ }
38
+
39
+ type TraceAwareAxiosRequestConfig = InternalAxiosRequestConfig & {
40
+ __slatesHttpTraceDraft?: SlateHttpTraceDraft;
41
+ };
42
+
43
+ let traceDraftsByConfig = new WeakMap<object, SlateHttpTraceDraft>();
44
+ let traceDraftsByHeaders = new WeakMap<object, SlateHttpTraceDraft>();
45
+
46
+ let TRACE_TEXT_LIMIT = 10 * 1024;
47
+ let REDACTED_VALUE = '[redacted]';
48
+ let STRUCTURED_DEPTH_LIMIT = 6;
49
+ let STRUCTURED_ENTRY_LIMIT = 50;
50
+
51
+ // Atoms that identify a header/field/param name as containing a secret.
52
+ // These are matched with token boundaries so `sig` flags `x-sig-key` but NOT `configure`,
53
+ // and `code` only flags `code`/`auth_code`/etc., never `encode`/`decoder`.
54
+ // `[-_.]?` allows compound atoms to match either with or without a separator
55
+ // (e.g. `apikey`, `api-key`, `api_key`, `api.key`).
56
+ let CORE_SECRET_KEY_ATOMS = [
57
+ // HTTP authentication
58
+ 'authorization',
59
+ 'proxy[-_.]?authorization',
60
+ 'www[-_.]?authenticate',
61
+ 'bearer',
62
+
63
+ // Generic tokens
64
+ 'token',
65
+ 'tokens',
66
+ 'access[-_.]?token',
67
+ 'refresh[-_.]?token',
68
+ 'id[-_.]?token',
69
+ 'auth[-_.]?token',
70
+ 'bearer[-_.]?token',
71
+ 'session[-_.]?token',
72
+ 'api[-_.]?token',
73
+ 'app[-_.]?token',
74
+ 'user[-_.]?token',
75
+ 'oauth[-_.]?token',
76
+ 'personal[-_.]?access[-_.]?token',
77
+ 'x[-_.]?auth[-_.]?token',
78
+ 'x[-_.]?access[-_.]?token',
79
+ 'x[-_.]?refresh[-_.]?token',
80
+
81
+ // API keys
82
+ 'apikey',
83
+ 'api[-_.]?key',
84
+ 'app[-_.]?key',
85
+ 'x[-_.]?api[-_.]?key',
86
+ 'x[-_.]?app[-_.]?key',
87
+
88
+ // OAuth client credentials
89
+ 'client[-_.]?secret',
90
+ 'client[-_.]?key',
91
+ 'consumer[-_.]?secret',
92
+ 'consumer[-_.]?key',
93
+
94
+ // Cryptographic keys
95
+ 'private[-_.]?key',
96
+ 'privatekey',
97
+ 'secret[-_.]?key',
98
+ 'secretkey',
99
+ 'secret[-_.]?access[-_.]?key',
100
+ 'encryption[-_.]?key',
101
+ 'decryption[-_.]?key',
102
+ 'signing[-_.]?key',
103
+ 'signing[-_.]?secret',
104
+ 'master[-_.]?key',
105
+ 'master[-_.]?secret',
106
+ 'master[-_.]?password',
107
+ 'shared[-_.]?secret',
108
+ 'webhook[-_.]?secret',
109
+ 'webhook[-_.]?signing[-_.]?secret',
110
+
111
+ // OAuth codes & PKCE
112
+ 'auth[-_.]?code',
113
+ 'authcode',
114
+ 'authorization[-_.]?code',
115
+ 'code[-_.]?verifier',
116
+ 'code[-_.]?challenge',
117
+
118
+ // Passwords & passcodes
119
+ 'password',
120
+ 'passwd',
121
+ 'pwd',
122
+ 'passphrase',
123
+ 'pass[-_.]?phrase',
124
+ 'pin',
125
+ 'passcode',
126
+
127
+ // Generic secret markers
128
+ 'secret',
129
+ 'secrets',
130
+ 'credential',
131
+ 'credentials',
132
+ 'creds',
133
+ 'auth',
134
+ 'x[-_.]?auth',
135
+ 'x[-_.]?auth[-_.]?key',
136
+
137
+ // CSRF / XSRF
138
+ 'csrf',
139
+ 'xsrf',
140
+ 'csrf[-_.]?token',
141
+ 'xsrf[-_.]?token',
142
+ 'x[-_.]?csrf[-_.]?token',
143
+ 'x[-_.]?xsrf[-_.]?token',
144
+
145
+ // JWT
146
+ 'jwt',
147
+
148
+ // Sessions & cookies
149
+ 'session[-_.]?id',
150
+ 'sessionid',
151
+ 'sid',
152
+ 'cookie',
153
+ 'cookies',
154
+ 'set[-_.]?cookie',
155
+
156
+ // Signatures / MAC
157
+ 'signature',
158
+ 'x[-_.]?hub[-_.]?signature',
159
+ 'x[-_.]?signature',
160
+ 'sig',
161
+ 'hmac',
162
+ 'mac',
163
+
164
+ // Crypto artifacts
165
+ 'salt',
166
+ 'nonce',
167
+
168
+ // Multi-factor / verification
169
+ 'otp',
170
+ 'totp',
171
+ 'mfa',
172
+ 'mfa[-_.]?code',
173
+ '2fa',
174
+ 'two[-_.]?factor',
175
+ 'twofactor',
176
+ 'verification[-_.]?code',
177
+ 'verify[-_.]?code',
178
+ 'confirm(?:ation)?[-_.]?code',
179
+ 'sms[-_.]?code',
180
+ 'email[-_.]?code',
181
+
182
+ // Recovery & seed phrases
183
+ 'seed[-_.]?phrase',
184
+ 'mnemonic',
185
+ 'recovery[-_.]?key',
186
+ 'recovery[-_.]?code',
187
+ 'recovery[-_.]?phrase',
188
+
189
+ // PII / financial
190
+ 'card[-_.]?number',
191
+ 'card[-_.]?code',
192
+ 'cvv',
193
+ 'cvc',
194
+ 'ccv',
195
+ 'cc[-_.]?num(?:ber)?',
196
+ 'pan',
197
+ 'ssn',
198
+ 'social[-_.]?security',
199
+ 'license[-_.]?key'
200
+ ];
201
+
202
+ // Extra atoms that are considered secret-like specifically in URL query strings
203
+ // and x-www-form-urlencoded bodies, where a lone `code` typically carries an OAuth
204
+ // authorization code. We keep `code` out of CORE to avoid redacting `{"code": 404}`
205
+ // in structured JSON responses.
206
+ let URL_FORM_SECRET_KEY_ATOMS = [...CORE_SECRET_KEY_ATOMS, 'code'];
207
+
208
+ let buildKeyBoundaryPattern = (atoms: string[], flags: string) =>
209
+ new RegExp(`(?:^|[^A-Za-z0-9])(?:${atoms.join('|')})(?=[^A-Za-z0-9]|$)`, flags);
210
+
211
+ let SECRET_KEY_PATTERN = buildKeyBoundaryPattern(CORE_SECRET_KEY_ATOMS, 'i');
212
+ let URL_FORM_SECRET_KEY_PATTERN = buildKeyBoundaryPattern(URL_FORM_SECRET_KEY_ATOMS, 'i');
213
+
214
+ // Explicit non-secret keys that would otherwise trip the patterns above because
215
+ // they embed a secret-sounding token (e.g. `token_type` contains `token`).
216
+ // Keys here describe the *kind* of a secret, not the secret itself.
217
+ let NON_SECRET_KEY_EXACT = new Set<string>([
218
+ 'token_type',
219
+ 'token-type',
220
+ 'tokentype',
221
+ 'token_kind',
222
+ 'token-kind',
223
+ 'tokenkind',
224
+ 'token_ttl',
225
+ 'token-ttl',
226
+ 'token_expires_in',
227
+ 'token-expires-in',
228
+ 'token_expiry',
229
+ 'signature_method',
230
+ 'signature-method',
231
+ 'signature_type',
232
+ 'signature-type',
233
+ 'signature_version',
234
+ 'signature-version',
235
+ 'auth_type',
236
+ 'auth-type',
237
+ 'auth_method',
238
+ 'auth-method',
239
+ 'authentication_type',
240
+ 'authentication-type',
241
+ 'authentication_method',
242
+ 'authentication-method',
243
+ 'grant_type',
244
+ 'grant-type',
245
+ 'password_strength',
246
+ 'password-strength',
247
+ 'password_policy',
248
+ 'password-policy',
249
+ 'key_type',
250
+ 'key-type',
251
+ 'key_name',
252
+ 'key-name',
253
+ 'key_id',
254
+ 'key-id',
255
+ 'kid'
256
+ ]);
257
+
258
+ let isSecretKeyName = (name: string): boolean => {
259
+ let normalized = name.toLowerCase();
260
+ if (NON_SECRET_KEY_EXACT.has(normalized)) return false;
261
+ return SECRET_KEY_PATTERN.test(normalized);
262
+ };
263
+
264
+ let isUrlFormSecretKeyName = (name: string): boolean => {
265
+ let normalized = name.toLowerCase();
266
+ if (NON_SECRET_KEY_EXACT.has(normalized)) return false;
267
+ return URL_FORM_SECRET_KEY_PATTERN.test(normalized);
268
+ };
269
+
270
+ // Patterns that identify well-known secret values regardless of the surrounding key.
271
+ // Keeps low false-positive risk by matching formats that are unlikely to occur otherwise.
272
+ let KNOWN_SECRET_VALUE_PATTERNS: RegExp[] = [
273
+ // JSON Web Tokens (header.payload.signature)
274
+ /\beyJ[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}\b/g,
275
+ // AWS access key IDs
276
+ /\b(?:AKIA|ASIA|AGPA|AIDA|ANPA|ANVA|AROA|APKA|ASCA)[A-Z0-9]{16}\b/g,
277
+ // GitHub tokens (PAT / OAuth / server-to-server / user-to-server / refresh)
278
+ /\bgh[pousr]_[A-Za-z0-9]{30,}\b/g,
279
+ // Slack tokens
280
+ /\bxox[abprs]-[A-Za-z0-9-]{10,}\b/g,
281
+ // Stripe secret / public / restricted keys
282
+ /\b(?:sk|rk|pk)_(?:test|live)_[A-Za-z0-9]{16,}\b/g,
283
+ // OpenAI API keys
284
+ /\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/g,
285
+ // Anthropic API keys
286
+ /\bsk-ant-[A-Za-z0-9_-]{20,}\b/g,
287
+ // Google API keys
288
+ /\bAIza[A-Za-z0-9_-]{35}\b/g,
289
+ // SendGrid API keys
290
+ /\bSG\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\b/g,
291
+ // Twilio API keys
292
+ /\bSK[0-9a-fA-F]{32}\b/g,
293
+ // PEM-encoded private keys (multi-line)
294
+ /-----BEGIN[A-Z ]*PRIVATE KEY-----[\s\S]*?-----END[A-Z ]*PRIVATE KEY-----/g,
295
+ // SSH private keys (OpenSSH format)
296
+ /-----BEGIN OPENSSH PRIVATE KEY-----[\s\S]*?-----END OPENSSH PRIVATE KEY-----/g
297
+ ];
298
+
299
+ // HTTP authentication schemes in free text, e.g. "Authorization: Bearer eyJ..."
300
+ let HTTP_AUTH_SCHEME_PATTERN =
301
+ /\b(bearer|basic|digest|token|negotiate|apikey)\s+([A-Za-z0-9_\-.~+/=]{4,})/gi;
302
+
303
+ // Generic `key=value` scan; the key is filtered via isUrlFormSecretKeyName at replace time
304
+ // so we can apply a deny-list for compound keys like `token_type` or `grant_type`.
305
+ let GENERIC_KEY_VALUE_PATTERN =
306
+ /(?<![A-Za-z0-9])([A-Za-z0-9][A-Za-z0-9_\-.]*)(\s*=\s*)([^&#\s;]+)/g;
307
+
308
+ // Generic `"key": "value"` scan; the key is filtered via isSecretKeyName at replace time.
309
+ let GENERIC_JSON_KEY_VALUE_PATTERN =
310
+ /"([^"\\]*(?:\\.[^"\\]*)*)"(\s*:\s*)"([^"\\]*(?:\\.[^"\\]*)*)"/g;
311
+
312
+ let SAFE_HEADER_NAMES = new Set([
313
+ 'accept',
314
+ 'accept-encoding',
315
+ 'accept-language',
316
+ 'cache-control',
317
+ 'connection',
318
+ 'content-encoding',
319
+ 'content-language',
320
+ 'content-length',
321
+ 'content-type',
322
+ 'date',
323
+ 'etag',
324
+ 'expires',
325
+ 'if-modified-since',
326
+ 'if-none-match',
327
+ 'keep-alive',
328
+ 'last-modified',
329
+ 'location',
330
+ 'origin',
331
+ 'pragma',
332
+ 'referer',
333
+ 'referrer-policy',
334
+ 'request-id',
335
+ 'retry-after',
336
+ 'server',
337
+ 'transfer-encoding',
338
+ 'user-agent',
339
+ 'vary',
340
+ 'via',
341
+ 'x-correlation-id',
342
+ 'x-powered-by',
343
+ 'x-request-id',
344
+ 'x-slates-provider',
345
+ 'x-trace-id'
346
+ ]);
347
+
348
+ let SAFE_HEADER_PREFIXES = ['ratelimit-', 'x-ratelimit-'];
349
+
350
+ let isRecord = (value: unknown): value is Record<string, unknown> =>
351
+ typeof value === 'object' && value !== null && !Array.isArray(value);
352
+
353
+ let isObjectLike = (value: unknown): value is object =>
354
+ typeof value === 'object' && value !== null;
355
+
356
+ let normalizeContentType = (value: string | undefined) =>
357
+ value?.split(';')[0]?.trim().toLowerCase() || undefined;
358
+
359
+ let isTextContentType = (contentType?: string) =>
360
+ !contentType ||
361
+ /(^text\/)|json|xml|html|javascript|graphql|x-www-form-urlencoded|svg/.test(contentType);
362
+
363
+ let isSafeHeaderName = (name: string) => {
364
+ let normalized = name.toLowerCase();
365
+ if (isSecretKeyName(normalized)) return false;
366
+ if (SAFE_HEADER_NAMES.has(normalized)) return true;
367
+ return SAFE_HEADER_PREFIXES.some(prefix => normalized.startsWith(prefix));
368
+ };
369
+
370
+ let truncateText = (text: string) => {
371
+ if (text.length <= TRACE_TEXT_LIMIT) {
372
+ return { text, truncated: false };
373
+ }
374
+
375
+ return {
376
+ text: `${text.slice(0, TRACE_TEXT_LIMIT)}...[truncated]`,
377
+ truncated: true
378
+ };
379
+ };
380
+
381
+ let sanitizeFreeText = (text: string, redactor?: AuthConfigSecretRedactor) => {
382
+ if (!text) return text;
383
+
384
+ // Literal auth-config secret values are replaced first -- this catches secrets sitting
385
+ // under a key name the heuristics below don't recognize as secret-like.
386
+ let result = redactor ? redactor.redact(text) : text;
387
+
388
+ result = result.replace(
389
+ HTTP_AUTH_SCHEME_PATTERN,
390
+ (_match, scheme: string) => `${scheme} ${REDACTED_VALUE}`
391
+ );
392
+
393
+ for (let pattern of KNOWN_SECRET_VALUE_PATTERNS) {
394
+ result = result.replace(pattern, REDACTED_VALUE);
395
+ }
396
+
397
+ result = result.replace(
398
+ GENERIC_KEY_VALUE_PATTERN,
399
+ (match, key: string, equals: string, _value: string) =>
400
+ isUrlFormSecretKeyName(key) ? `${key}${equals}${REDACTED_VALUE}` : match
401
+ );
402
+
403
+ result = result.replace(
404
+ GENERIC_JSON_KEY_VALUE_PATTERN,
405
+ (match, key: string, colon: string, _value: string) =>
406
+ isSecretKeyName(key) ? `"${key}"${colon}"${REDACTED_VALUE}"` : match
407
+ );
408
+
409
+ return result;
410
+ };
411
+
412
+ let sanitizeScalar = (value: string, redactor?: AuthConfigSecretRedactor) =>
413
+ truncateText(sanitizeFreeText(value, redactor));
414
+
415
+ let redactStructuredValue = (
416
+ value: unknown,
417
+ depth = 0,
418
+ redactor?: AuthConfigSecretRedactor
419
+ ): unknown => {
420
+ if (value == null) return value;
421
+ if (depth >= STRUCTURED_DEPTH_LIMIT) return '[truncated]';
422
+
423
+ if (
424
+ typeof value === 'string' ||
425
+ typeof value === 'number' ||
426
+ typeof value === 'boolean' ||
427
+ typeof value === 'bigint'
428
+ ) {
429
+ return typeof value === 'string' ? sanitizeFreeText(value, redactor) : value;
430
+ }
431
+
432
+ if (Array.isArray(value)) {
433
+ return value
434
+ .slice(0, STRUCTURED_ENTRY_LIMIT)
435
+ .map(entry => redactStructuredValue(entry, depth + 1, redactor));
436
+ }
437
+
438
+ if (isRecord(value)) {
439
+ return Object.fromEntries(
440
+ Object.entries(value)
441
+ .slice(0, STRUCTURED_ENTRY_LIMIT)
442
+ .map(([key, entry]) => [
443
+ key,
444
+ isSecretKeyName(key)
445
+ ? REDACTED_VALUE
446
+ : redactStructuredValue(entry, depth + 1, redactor)
447
+ ])
448
+ );
449
+ }
450
+
451
+ return String(value);
452
+ };
453
+
454
+ let maybeParseJson = (text: string, contentType?: string) => {
455
+ let trimmed = text.trim();
456
+ if (!trimmed) return null;
457
+ if (!contentType?.includes('json') && !/^[[{]/.test(trimmed)) {
458
+ return null;
459
+ }
460
+
461
+ try {
462
+ return JSON.parse(trimmed);
463
+ } catch {
464
+ return null;
465
+ }
466
+ };
467
+
468
+ let sanitizeTextBody = (
469
+ text: string,
470
+ contentType?: string,
471
+ redactor?: AuthConfigSecretRedactor
472
+ ): SlateHttpTraceTextBody | undefined => {
473
+ if (!text) return undefined;
474
+
475
+ let normalizedContentType = normalizeContentType(contentType);
476
+ let parsedJson = maybeParseJson(text, normalizedContentType);
477
+ let normalizedText =
478
+ parsedJson !== null
479
+ ? JSON.stringify(redactStructuredValue(parsedJson, 0, redactor))
480
+ : sanitizeFreeText(text, redactor);
481
+ let { text: truncatedText, truncated } = truncateText(normalizedText);
482
+
483
+ return {
484
+ ...(normalizedContentType ? { contentType: normalizedContentType } : {}),
485
+ text: truncatedText,
486
+ ...(truncated ? { truncated: true } : {})
487
+ };
488
+ };
489
+
490
+ let isBinaryLike = (value: unknown) => {
491
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) return true;
492
+ if (value instanceof ArrayBuffer) return true;
493
+ if (ArrayBuffer.isView(value)) return true;
494
+ if (typeof Blob !== 'undefined' && value instanceof Blob) return true;
495
+ if (typeof FormData !== 'undefined' && value instanceof FormData) return true;
496
+ return false;
497
+ };
498
+
499
+ let serializeTextBody = (
500
+ value: unknown,
501
+ contentType?: string,
502
+ redactor?: AuthConfigSecretRedactor
503
+ ): SlateHttpTraceTextBody | undefined => {
504
+ let normalizedContentType = normalizeContentType(contentType);
505
+
506
+ if (value == null || isBinaryLike(value) || !isTextContentType(normalizedContentType)) {
507
+ return undefined;
508
+ }
509
+
510
+ if (typeof value === 'string') {
511
+ return sanitizeTextBody(value, normalizedContentType, redactor);
512
+ }
513
+
514
+ if (value instanceof URLSearchParams) {
515
+ return sanitizeTextBody(value.toString(), normalizedContentType, redactor);
516
+ }
517
+
518
+ if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
519
+ return sanitizeTextBody(String(value), normalizedContentType, redactor);
520
+ }
521
+
522
+ if (Array.isArray(value) || isRecord(value)) {
523
+ return sanitizeTextBody(
524
+ JSON.stringify(redactStructuredValue(value, 0, redactor)),
525
+ 'application/json',
526
+ redactor
527
+ );
528
+ }
529
+
530
+ return undefined;
531
+ };
532
+
533
+ let buildUrl = (baseURL: string | undefined, url: string | undefined) => {
534
+ if (!url) return '';
535
+ if (!baseURL) return url;
536
+
537
+ try {
538
+ return new URL(url, baseURL).toString();
539
+ } catch {
540
+ return url;
541
+ }
542
+ };
543
+
544
+ // URLSearchParams percent-encodes `[` and `]`, which makes `[redacted]` render as
545
+ // `%5Bredacted%5D`. Undo that specific encoding so traces stay readable.
546
+ let REDACTED_ENCODED = encodeURIComponent(REDACTED_VALUE);
547
+ let decodeRedactedMarker = (value: string) =>
548
+ REDACTED_ENCODED === REDACTED_VALUE
549
+ ? value
550
+ : value.split(REDACTED_ENCODED).join(REDACTED_VALUE);
551
+
552
+ let redactHashFragment = (hash: string, redactor?: AuthConfigSecretRedactor) => {
553
+ if (!hash?.includes('=')) return hash;
554
+ let withoutHash = hash.startsWith('#') ? hash.slice(1) : hash;
555
+ let redacted = withoutHash.replace(
556
+ /(?<![A-Za-z0-9])([A-Za-z0-9][A-Za-z0-9_\-.]*)=([^&]*)/g,
557
+ (match, key: string, rawValue: string) => {
558
+ if (isUrlFormSecretKeyName(key)) return `${key}=${REDACTED_VALUE}`;
559
+ let literalRedacted = redactor ? redactor.redact(rawValue) : rawValue;
560
+ return literalRedacted !== rawValue ? `${key}=${literalRedacted}` : match;
561
+ }
562
+ );
563
+ return redacted ? `#${redacted}` : '';
564
+ };
565
+
566
+ let sanitizeUrl = (value: string, redactor?: AuthConfigSecretRedactor) => {
567
+ if (!value) return value;
568
+
569
+ try {
570
+ let url = new URL(value);
571
+
572
+ if (url.username || url.password) {
573
+ url.username = '';
574
+ url.password = '';
575
+ }
576
+
577
+ for (let key of Array.from(url.searchParams.keys())) {
578
+ if (isUrlFormSecretKeyName(key)) {
579
+ url.searchParams.set(key, REDACTED_VALUE);
580
+ continue;
581
+ }
582
+
583
+ // Not a secret-looking key -- still catch a literal auth-config secret value.
584
+ let rawValue = url.searchParams.get(key) ?? '';
585
+ let literalRedacted = redactor ? redactor.redact(rawValue) : rawValue;
586
+ if (literalRedacted !== rawValue) url.searchParams.set(key, literalRedacted);
587
+ }
588
+
589
+ url.hash = redactHashFragment(url.hash, redactor);
590
+
591
+ return decodeRedactedMarker(url.toString());
592
+ } catch {
593
+ let text = redactor ? redactor.redact(value) : value;
594
+ return text.replace(
595
+ /([?&]([^=&#]+)=)([^&#]+)/g,
596
+ (_match, prefix: string, key: string, rawValue: string) =>
597
+ isUrlFormSecretKeyName(key) ? `${prefix}${REDACTED_VALUE}` : `${prefix}${rawValue}`
598
+ );
599
+ }
600
+ };
601
+
602
+ let flattenHeaderValue = (value: unknown): string | undefined => {
603
+ if (typeof value === 'string') return value;
604
+ if (typeof value === 'number' || typeof value === 'boolean') return String(value);
605
+ if (Array.isArray(value)) {
606
+ let normalized = value
607
+ .map(entry => flattenHeaderValue(entry))
608
+ .filter((entry): entry is string => typeof entry === 'string' && entry.length > 0);
609
+ return normalized.length > 0 ? normalized.join(', ') : undefined;
610
+ }
611
+ return undefined;
612
+ };
613
+
614
+ let headerEntries = (headers: unknown): [string, string][] => {
615
+ if (!headers) return [];
616
+
617
+ if (typeof (headers as { toJSON?: unknown }).toJSON === 'function') {
618
+ let json = (headers as { toJSON(): unknown }).toJSON();
619
+ if (isRecord(json)) {
620
+ return Object.entries(json)
621
+ .map(([key, value]) => {
622
+ let flattened = flattenHeaderValue(value);
623
+ return flattened ? ([key, flattened] as [string, string]) : null;
624
+ })
625
+ .filter((entry): entry is [string, string] => entry !== null);
626
+ }
627
+ }
628
+
629
+ if (typeof (headers as { entries?: unknown }).entries === 'function') {
630
+ return Array.from(
631
+ (headers as { entries(): IterableIterator<[string, string]> }).entries()
632
+ ).map(([key, value]) => [key, value]);
633
+ }
634
+
635
+ if (isRecord(headers)) {
636
+ return Object.entries(headers)
637
+ .map(([key, value]) => {
638
+ let flattened = flattenHeaderValue(value);
639
+ return flattened ? ([key, flattened] as [string, string]) : null;
640
+ })
641
+ .filter((entry): entry is [string, string] => entry !== null);
642
+ }
643
+
644
+ return [];
645
+ };
646
+
647
+ let sanitizeHeaders = (headers: unknown, redactor?: AuthConfigSecretRedactor) => {
648
+ let entries = headerEntries(headers)
649
+ .map(
650
+ ([key, value]) =>
651
+ [key.toLowerCase(), sanitizeScalar(value, redactor).text] as [string, string]
652
+ )
653
+ .filter(([key]) => isSafeHeaderName(key));
654
+
655
+ if (entries.length === 0) return undefined;
656
+ return Object.fromEntries(entries);
657
+ };
658
+
659
+ let getContentType = (headers: unknown) =>
660
+ headerEntries(headers).find(([key]) => key.toLowerCase() === 'content-type')?.[1];
661
+
662
+ let takeTraceDraft = (config: TraceAwareAxiosRequestConfig | undefined) => {
663
+ if (!config) return undefined;
664
+
665
+ let draft =
666
+ config.__slatesHttpTraceDraft ??
667
+ (isObjectLike(config) ? traceDraftsByConfig.get(config) : undefined) ??
668
+ (isObjectLike(config.headers) ? traceDraftsByHeaders.get(config.headers) : undefined);
669
+
670
+ if (!draft) return undefined;
671
+
672
+ return draft;
673
+ };
674
+
675
+ let clearTraceDraft = (
676
+ config: TraceAwareAxiosRequestConfig | undefined,
677
+ draft: SlateHttpTraceDraft
678
+ ) => {
679
+ if (!config) return;
680
+
681
+ if (config.__slatesHttpTraceDraft === draft) {
682
+ config.__slatesHttpTraceDraft = undefined;
683
+ }
684
+
685
+ if (isObjectLike(config)) {
686
+ traceDraftsByConfig.delete(config);
687
+ }
688
+
689
+ if (isObjectLike(config.headers)) {
690
+ traceDraftsByHeaders.delete(config.headers);
691
+ }
692
+ };
693
+
694
+ export let attachHttpTraceDraft = (
695
+ config: InternalAxiosRequestConfig,
696
+ context: SlatePublicContext<any>
697
+ ) => {
698
+ let traceAwareConfig = config as TraceAwareAxiosRequestConfig;
699
+ let contentType = getContentType(config.headers);
700
+ let redactor = new AuthConfigSecretRedactor(context._getAuthConfigForRedaction());
701
+
702
+ traceAwareConfig.__slatesHttpTraceDraft = {
703
+ context,
704
+ startedAt: new Date().toISOString(),
705
+ startedAtMs: Date.now(),
706
+ request: {
707
+ method: (config.method ?? 'GET').toUpperCase(),
708
+ url: sanitizeUrl(buildUrl(config.baseURL, config.url), redactor),
709
+ ...(sanitizeHeaders(config.headers, redactor)
710
+ ? { headers: sanitizeHeaders(config.headers, redactor) }
711
+ : {}),
712
+ ...(serializeTextBody(config.data, contentType, redactor)
713
+ ? { body: serializeTextBody(config.data, contentType, redactor) }
714
+ : {})
715
+ }
716
+ };
717
+
718
+ if (isObjectLike(traceAwareConfig)) {
719
+ traceDraftsByConfig.set(traceAwareConfig, traceAwareConfig.__slatesHttpTraceDraft);
720
+ }
721
+
722
+ if (isObjectLike(traceAwareConfig.headers)) {
723
+ traceDraftsByHeaders.set(
724
+ traceAwareConfig.headers,
725
+ traceAwareConfig.__slatesHttpTraceDraft
726
+ );
727
+ }
728
+
729
+ return config;
730
+ };
731
+
732
+ export let recordHttpTraceFromResponse = (response: AxiosResponse) => {
733
+ let config = response.config as TraceAwareAxiosRequestConfig | undefined;
734
+ let draft = takeTraceDraft(config);
735
+ if (!draft) return response;
736
+ clearTraceDraft(config, draft);
737
+
738
+ let redactor = new AuthConfigSecretRedactor(draft.context._getAuthConfigForRedaction());
739
+ let contentType = getContentType(response.headers);
740
+ draft.context.recordHttpTrace({
741
+ startedAt: draft.startedAt,
742
+ durationMs: Math.max(Date.now() - draft.startedAtMs, 0),
743
+ request: draft.request,
744
+ response: {
745
+ status: response.status,
746
+ ...(response.statusText ? { statusText: response.statusText } : {}),
747
+ ...(sanitizeHeaders(response.headers, redactor)
748
+ ? { headers: sanitizeHeaders(response.headers, redactor) }
749
+ : {}),
750
+ ...(serializeTextBody(response.data, contentType, redactor)
751
+ ? { body: serializeTextBody(response.data, contentType, redactor) }
752
+ : {})
753
+ }
754
+ });
755
+
756
+ return response;
757
+ };
758
+
759
+ export let recordHttpTraceFromError = (error: AxiosError) => {
760
+ let config = error.config as TraceAwareAxiosRequestConfig | undefined;
761
+ let draft = takeTraceDraft(config);
762
+ if (!draft) return;
763
+ clearTraceDraft(config, draft);
764
+
765
+ let redactor = new AuthConfigSecretRedactor(draft.context._getAuthConfigForRedaction());
766
+ let responseContentType = getContentType(error.response?.headers);
767
+ draft.context.recordHttpTrace({
768
+ startedAt: draft.startedAt,
769
+ durationMs: Math.max(Date.now() - draft.startedAtMs, 0),
770
+ request: draft.request,
771
+ ...(error.response
772
+ ? {
773
+ response: {
774
+ status: error.response.status,
775
+ ...(error.response.statusText ? { statusText: error.response.statusText } : {}),
776
+ ...(sanitizeHeaders(error.response.headers, redactor)
777
+ ? { headers: sanitizeHeaders(error.response.headers, redactor) }
778
+ : {}),
779
+ ...(serializeTextBody(error.response.data, responseContentType, redactor)
780
+ ? { body: serializeTextBody(error.response.data, responseContentType, redactor) }
781
+ : {})
782
+ }
783
+ }
784
+ : {}),
785
+ error: {
786
+ ...(error.code ? { code: error.code } : {}),
787
+ message: error.message
788
+ }
789
+ });
790
+ };
791
+
792
+ // Exposed for unit tests. Not part of the public API.
793
+ export let __traceInternals = {
794
+ sanitizeFreeText,
795
+ sanitizeUrl,
796
+ sanitizeHeaders,
797
+ isSafeHeaderName,
798
+ isSecretKeyName,
799
+ isUrlFormSecretKeyName,
800
+ redactStructuredValue,
801
+ SECRET_KEY_PATTERN,
802
+ URL_FORM_SECRET_KEY_PATTERN
803
+ };