@librechat/agents 3.3.0 → 3.3.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.
Files changed (129) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +116 -2
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/common/enum.cjs +2 -0
  4. package/dist/cjs/common/enum.cjs.map +1 -1
  5. package/dist/cjs/graphs/Graph.cjs +229 -48
  6. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  7. package/dist/cjs/graphs/MultiAgentGraph.cjs +102 -27
  8. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  9. package/dist/cjs/llm/bedrock/index.cjs +11 -2
  10. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  11. package/dist/cjs/llm/contextOverflowRecovery.cjs +132 -0
  12. package/dist/cjs/llm/contextOverflowRecovery.cjs.map +1 -0
  13. package/dist/cjs/llm/google/index.cjs +1 -1
  14. package/dist/cjs/llm/invoke.cjs +60 -3
  15. package/dist/cjs/llm/invoke.cjs.map +1 -1
  16. package/dist/cjs/main.cjs +20 -7
  17. package/dist/cjs/messages/prune.cjs +16 -5
  18. package/dist/cjs/messages/prune.cjs.map +1 -1
  19. package/dist/cjs/messages/recency.cjs +2 -0
  20. package/dist/cjs/messages/recency.cjs.map +1 -1
  21. package/dist/cjs/run.cjs +15 -2
  22. package/dist/cjs/run.cjs.map +1 -1
  23. package/dist/cjs/stream.cjs +205 -49
  24. package/dist/cjs/stream.cjs.map +1 -1
  25. package/dist/cjs/summarization/node.cjs +55 -7
  26. package/dist/cjs/summarization/node.cjs.map +1 -1
  27. package/dist/cjs/tools/BashExecutor.cjs +2 -2
  28. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  29. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +2 -2
  30. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  31. package/dist/cjs/tools/CodeExecutor.cjs +69 -8
  32. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  33. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +19 -15
  34. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  35. package/dist/cjs/tools/ToolNode.cjs +32 -3
  36. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  37. package/dist/cjs/utils/errors.cjs +317 -52
  38. package/dist/cjs/utils/errors.cjs.map +1 -1
  39. package/dist/esm/agents/AgentContext.mjs +117 -3
  40. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  41. package/dist/esm/common/enum.mjs +2 -0
  42. package/dist/esm/common/enum.mjs.map +1 -1
  43. package/dist/esm/graphs/Graph.mjs +227 -46
  44. package/dist/esm/graphs/Graph.mjs.map +1 -1
  45. package/dist/esm/graphs/MultiAgentGraph.mjs +102 -27
  46. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  47. package/dist/esm/llm/bedrock/index.mjs +11 -2
  48. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  49. package/dist/esm/llm/contextOverflowRecovery.mjs +130 -0
  50. package/dist/esm/llm/contextOverflowRecovery.mjs.map +1 -0
  51. package/dist/esm/llm/google/index.mjs +1 -1
  52. package/dist/esm/llm/invoke.mjs +59 -4
  53. package/dist/esm/llm/invoke.mjs.map +1 -1
  54. package/dist/esm/main.mjs +9 -9
  55. package/dist/esm/messages/prune.mjs +14 -6
  56. package/dist/esm/messages/prune.mjs.map +1 -1
  57. package/dist/esm/messages/recency.mjs +2 -1
  58. package/dist/esm/messages/recency.mjs.map +1 -1
  59. package/dist/esm/run.mjs +15 -2
  60. package/dist/esm/run.mjs.map +1 -1
  61. package/dist/esm/stream.mjs +205 -49
  62. package/dist/esm/stream.mjs.map +1 -1
  63. package/dist/esm/summarization/node.mjs +55 -7
  64. package/dist/esm/summarization/node.mjs.map +1 -1
  65. package/dist/esm/tools/BashExecutor.mjs +3 -3
  66. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  67. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +3 -3
  68. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  69. package/dist/esm/tools/CodeExecutor.mjs +62 -9
  70. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  71. package/dist/esm/tools/ProgrammaticToolCalling.mjs +20 -16
  72. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  73. package/dist/esm/tools/ToolNode.mjs +32 -3
  74. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  75. package/dist/esm/utils/errors.mjs +317 -53
  76. package/dist/esm/utils/errors.mjs.map +1 -1
  77. package/dist/types/agents/AgentContext.d.ts +62 -3
  78. package/dist/types/common/enum.d.ts +2 -0
  79. package/dist/types/graphs/Graph.d.ts +16 -2
  80. package/dist/types/graphs/MultiAgentGraph.d.ts +1 -0
  81. package/dist/types/llm/contextOverflowRecovery.d.ts +85 -0
  82. package/dist/types/llm/invoke.d.ts +35 -6
  83. package/dist/types/messages/prune.d.ts +10 -2
  84. package/dist/types/messages/recency.d.ts +1 -0
  85. package/dist/types/run.d.ts +2 -0
  86. package/dist/types/tools/CodeExecutor.d.ts +14 -1
  87. package/dist/types/types/llm.d.ts +7 -4
  88. package/dist/types/types/stream.d.ts +5 -4
  89. package/dist/types/types/summarize.d.ts +22 -0
  90. package/dist/types/utils/__tests__/fixtures/contextOverflowSignatures.d.ts +40 -0
  91. package/dist/types/utils/errors.d.ts +65 -16
  92. package/dist/types/utils/redactSecrets.d.ts +3 -0
  93. package/package.json +7 -8
  94. package/src/agents/AgentContext.ts +188 -7
  95. package/src/agents/__tests__/AgentContext.overflow.test.ts +205 -0
  96. package/src/common/enum.ts +2 -0
  97. package/src/graphs/Graph.ts +389 -58
  98. package/src/graphs/MultiAgentGraph.ts +184 -46
  99. package/src/graphs/__tests__/Graph.contextOverflow.test.ts +631 -0
  100. package/src/llm/__tests__/contextOverflowRecovery.test.ts +401 -0
  101. package/src/llm/__tests__/fallbackOverflow.test.ts +287 -0
  102. package/src/llm/bedrock/index.ts +25 -12
  103. package/src/llm/contextOverflowRecovery.ts +292 -0
  104. package/src/llm/invoke.ts +119 -4
  105. package/src/messages/prune.ts +24 -11
  106. package/src/messages/recency.ts +3 -1
  107. package/src/run.ts +24 -1
  108. package/src/scripts/context-overflow-probe.ts +997 -0
  109. package/src/specs/agent-handoffs.test.ts +903 -1
  110. package/src/specs/context-overflow-recovery.live.test.ts +213 -0
  111. package/src/splitStream.test.ts +882 -0
  112. package/src/stream.ts +315 -51
  113. package/src/summarization/__tests__/aggregator.test.ts +83 -0
  114. package/src/summarization/__tests__/node.test.ts +139 -0
  115. package/src/summarization/node.ts +99 -14
  116. package/src/tools/BashExecutor.ts +4 -2
  117. package/src/tools/BashProgrammaticToolCalling.ts +4 -7
  118. package/src/tools/CodeExecutor.ts +119 -8
  119. package/src/tools/ProgrammaticToolCalling.ts +29 -27
  120. package/src/tools/ToolNode.ts +50 -8
  121. package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +297 -3
  122. package/src/types/llm.ts +8 -1
  123. package/src/types/stream.ts +5 -4
  124. package/src/types/summarize.ts +22 -0
  125. package/src/utils/__tests__/errors.test.ts +270 -0
  126. package/src/utils/__tests__/fixtures/contextOverflowSignatures.ts +336 -0
  127. package/src/utils/__tests__/redactSecrets.test.ts +56 -0
  128. package/src/utils/errors.ts +484 -66
  129. package/src/utils/redactSecrets.ts +61 -0
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Provider error signatures captured from live over-limit requests.
3
+ *
4
+ * Every entry below was produced by `src/scripts/context-overflow-probe.ts`
5
+ * sending a prompt past the model's context window and recording what came
6
+ * back. Account identifiers and request ids have been replaced with
7
+ * placeholders; the wording, status codes, and nesting are verbatim.
8
+ *
9
+ * Treat these as evidence, not examples: if a provider changes its wording,
10
+ * re-run the probe and update the fixture rather than loosening the matcher.
11
+ */
12
+ import { Providers } from '@/common';
13
+
14
+ export interface OverflowSignature {
15
+ provider: Providers;
16
+ model: string;
17
+ /** Class the SDK actually threw, for reference in review. */
18
+ thrownAs: string;
19
+ /**
20
+ * Stand-in for the thrown error carrying exactly the fields the classifier
21
+ * reads. Nested bodies are kept as the strings the SDKs attach.
22
+ */
23
+ error: Record<string, unknown>;
24
+ expected: {
25
+ kind: 'context_window' | 'request_too_large';
26
+ limitTokens?: number;
27
+ requestedTokens?: number;
28
+ };
29
+ /** Set when the signature is only decidable with caller-side corroboration. */
30
+ requiresContextPressure?: boolean;
31
+ }
32
+
33
+ export const OVERFLOW_SIGNATURES: readonly OverflowSignature[] = [
34
+ {
35
+ provider: Providers.ANTHROPIC,
36
+ model: 'claude-haiku-4-5-20251001',
37
+ thrownAs: 'ContextOverflowError',
38
+ error: {
39
+ name: 'ContextOverflowError',
40
+ lc_error_code: 'CONTEXT_OVERFLOW',
41
+ message:
42
+ '400 {"type":"error","error":{"type":"invalid_request_error","message":"prompt is too long: 274468 tokens > 200000 maximum"},"request_id":"req_test"}\n\nTroubleshooting URL: https://docs.langchain.com/oss/javascript/langchain/errors/CONTEXT_OVERFLOW/\n',
43
+ cause:
44
+ '{"status":400,"headers":{},"requestID":"req_test","error":{"type":"error","error":{"type":"invalid_request_error","message":"prompt is too long: 274468 tokens > 200000 maximum"},"request_id":"req_test"},"type":"invalid_request_error"}',
45
+ },
46
+ expected: {
47
+ kind: 'context_window',
48
+ limitTokens: 200_000,
49
+ requestedTokens: 274_468,
50
+ },
51
+ },
52
+ {
53
+ provider: Providers.OPENAI,
54
+ model: 'gpt-4o-mini',
55
+ thrownAs: 'ContextOverflowError',
56
+ error: {
57
+ name: 'ContextOverflowError',
58
+ message:
59
+ '400 This model\'s maximum context length is 128000 tokens. However, your messages resulted in 149767 tokens. Please reduce the length of the messages.',
60
+ cause:
61
+ '{"status":400,"headers":{},"requestID":"req_test","error":{"message":"This model\'s maximum context length is 128000 tokens. However, your messages resulted in 149767 tokens. Please reduce the length of the messages.","type":"invalid_request_error","param":"messages","code":"context_length_exceeded"},"code":"context_length_exceeded","param":"messages","type":"invalid_request_error"}',
62
+ },
63
+ expected: {
64
+ kind: 'context_window',
65
+ limitTokens: 128_000,
66
+ requestedTokens: 149_767,
67
+ },
68
+ },
69
+ {
70
+ /**
71
+ * The common OpenAI symptom in practice: the request is large enough to
72
+ * overrun the per-minute token bucket, so it is rejected as a 429 before
73
+ * context validation ever runs. LangChain labels it MODEL_RATE_LIMIT.
74
+ */
75
+ provider: Providers.OPENAI,
76
+ model: 'gpt-5-nano',
77
+ thrownAs: 'RateLimitError',
78
+ error: {
79
+ name: 'Error',
80
+ status: 429,
81
+ code: 'rate_limit_exceeded',
82
+ type: 'tokens',
83
+ lc_error_code: 'MODEL_RATE_LIMIT',
84
+ message:
85
+ '429 Request too large for gpt-5-nano in organization org-test on tokens per min (TPM): Limit 200000, Requested 480002. The input or output tokens must be reduced in order to run successfully. Visit https://platform.openai.com/account/rate-limits to learn more.\n\nTroubleshooting URL: https://docs.langchain.com/oss/javascript/langchain/errors/MODEL_RATE_LIMIT/\n',
86
+ error:
87
+ '{"message":"Request too large for gpt-5-nano in organization org-test on tokens per min (TPM): Limit 200000, Requested 480002. The input or output tokens must be reduced in order to run successfully.","type":"tokens","param":null,"code":"rate_limit_exceeded"}',
88
+ },
89
+ expected: {
90
+ kind: 'request_too_large',
91
+ limitTokens: 200_000,
92
+ requestedTokens: 480_002,
93
+ },
94
+ },
95
+ {
96
+ provider: Providers.BEDROCK,
97
+ model: 'us.anthropic.claude-haiku-4-5-20251001-v1:0',
98
+ thrownAs: 'ValidationException',
99
+ error: {
100
+ name: 'ValidationException',
101
+ message:
102
+ 'The model returned the following errors: prompt is too long: 207848 tokens > 200000 maximum',
103
+ $metadata: { httpStatusCode: 400 },
104
+ },
105
+ expected: {
106
+ kind: 'context_window',
107
+ limitTokens: 200_000,
108
+ requestedTokens: 207_848,
109
+ },
110
+ },
111
+ {
112
+ /** Same provider, same model family, no numbers reported. */
113
+ provider: Providers.BEDROCK,
114
+ model: 'us.anthropic.claude-sonnet-4-5-20250929-v1:0',
115
+ thrownAs: 'ValidationException',
116
+ error: {
117
+ name: 'ValidationException',
118
+ message:
119
+ 'The model returned the following errors: Input is too long for requested model.',
120
+ $metadata: { httpStatusCode: 400 },
121
+ },
122
+ expected: { kind: 'context_window' },
123
+ },
124
+ {
125
+ /**
126
+ * Bedrock's most dangerous shape: the overflow arrives inside a
127
+ * successful HTTP 200 stream, so status-code-based detection is useless.
128
+ */
129
+ provider: Providers.BEDROCK,
130
+ model: 'us.amazon.nova-lite-v1:0',
131
+ thrownAs: 'Error',
132
+ error: {
133
+ name: 'Error',
134
+ message:
135
+ 'The model returned the following errors: Input Tokens Exceeded: Number of input tokens exceeds maximum length. Please update the input to try again.\n Deserialization error: to see the raw response, inspect the hidden field {error}.$response on this object.',
136
+ $metadata: { httpStatusCode: 200 },
137
+ },
138
+ expected: { kind: 'context_window' },
139
+ },
140
+ {
141
+ provider: Providers.BEDROCK,
142
+ model: 'us.meta.llama3-1-70b-instruct-v1:0',
143
+ thrownAs: 'Error',
144
+ error: {
145
+ name: 'Error',
146
+ message:
147
+ 'The model returned the following errors: This model\'s maximum context length is 131072 tokens. Please reduce the length of the prompt\n Deserialization error: to see the raw response, inspect the hidden field {error}.$response on this object.',
148
+ $metadata: { httpStatusCode: 200 },
149
+ },
150
+ expected: { kind: 'context_window', limitTokens: 131_072 },
151
+ },
152
+ {
153
+ provider: Providers.GOOGLE,
154
+ model: 'gemini-3.1-flash-image',
155
+ thrownAs: 'GoogleGenerativeAIFetchError',
156
+ error: {
157
+ name: 'Error',
158
+ status: 400,
159
+ message:
160
+ '[GoogleGenerativeAI Error]: Error fetching from https://generativelanguage.googleapis.com/v1beta/models/gemini-3.1-flash-image:streamGenerateContent?alt=sse: [400 Bad Request] The input token count exceeds the maximum number of tokens allowed (65536).',
161
+ },
162
+ expected: { kind: 'context_window', limitTokens: 65_536 },
163
+ },
164
+ {
165
+ /**
166
+ * Vertex AI's gaxios path discards the API error document, leaving a bare
167
+ * status line indistinguishable from any other 400 — the only signature
168
+ * in this set that needs caller-side corroboration.
169
+ */
170
+ provider: Providers.VERTEXAI,
171
+ model: 'gemini-2.5-flash-lite',
172
+ thrownAs: 'Error',
173
+ error: {
174
+ name: 'Error',
175
+ message: 'Google request failed with status code 400',
176
+ },
177
+ expected: { kind: 'context_window' },
178
+ requiresContextPressure: true,
179
+ },
180
+ {
181
+ provider: Providers.OPENROUTER,
182
+ model: 'qwen/qwen-2.5-7b-instruct',
183
+ thrownAs: 'ContextOverflowError',
184
+ error: {
185
+ name: 'ContextOverflowError',
186
+ message:
187
+ '400 This endpoint\'s maximum context length is 32768 tokens. However, you requested about 56827 tokens (56811 of text input, 16 in the output). Please reduce the length of either one, or use the context-compression plugin to compress your prompt automatically.',
188
+ cause:
189
+ '{"status":400,"headers":{},"requestID":null,"error":{"message":"This endpoint\'s maximum context length is 32768 tokens. However, you requested about 56827 tokens (56811 of text input, 16 in the output).","code":400,"metadata":{"provider_name":null}},"code":400}',
190
+ },
191
+ expected: {
192
+ kind: 'context_window',
193
+ limitTokens: 32_768,
194
+ requestedTokens: 56_827,
195
+ },
196
+ },
197
+ {
198
+ provider: Providers.DEEPSEEK,
199
+ model: 'deepseek-v4-flash',
200
+ thrownAs: 'ContextOverflowError',
201
+ error: {
202
+ name: 'ContextOverflowError',
203
+ message:
204
+ '400 This model\'s maximum context length is 1048565 tokens. However, you requested 1179668 tokens (1179652 in the messages, 16 in the completion). Please reduce the length of the messages or completion.',
205
+ cause:
206
+ '{"status":400,"headers":{},"requestID":null,"error":{"message":"This model\'s maximum context length is 1048565 tokens. However, you requested 1179668 tokens (1179652 in the messages, 16 in the completion).","type":"invalid_request_error","param":null,"code":"invalid_request_error"},"code":"invalid_request_error"}',
207
+ },
208
+ expected: {
209
+ kind: 'context_window',
210
+ limitTokens: 1_048_565,
211
+ requestedTokens: 1_179_668,
212
+ },
213
+ },
214
+ {
215
+ /** xAI says "prompt length", which LangChain's own matcher does not catch. */
216
+ provider: Providers.XAI,
217
+ model: 'grok-build-0.1',
218
+ thrownAs: 'BadRequestError',
219
+ error: {
220
+ name: 'Error',
221
+ status: 400,
222
+ message:
223
+ '400 "This model\'s maximum prompt length is 256000 but the request contains 332986 tokens."',
224
+ error:
225
+ 'This model\'s maximum prompt length is 256000 but the request contains 332986 tokens.',
226
+ },
227
+ expected: {
228
+ kind: 'context_window',
229
+ limitTokens: 256_000,
230
+ requestedTokens: 332_986,
231
+ },
232
+ },
233
+ {
234
+ provider: Providers.MISTRAL,
235
+ model: 'mistral-tiny-latest',
236
+ thrownAs: 'SDKError',
237
+ error: {
238
+ name: 'SDKError',
239
+ status: 400,
240
+ message:
241
+ 'API error occurred: Status 400 Content-Type application/json Body \n{"object":"error","message":"Prompt contains 170397 tokens and 0 draft tokens, too large for model with 131072 maximum context length","type":"invalid_request_invalid_args","param":null,"code":"3051","raw_status_code":400}',
242
+ body: '{"object":"error","message":"Prompt contains 170397 tokens and 0 draft tokens, too large for model with 131072 maximum context length","type":"invalid_request_invalid_args","param":null,"code":"3051","raw_status_code":400}',
243
+ },
244
+ expected: {
245
+ kind: 'context_window',
246
+ limitTokens: 131_072,
247
+ requestedTokens: 170_397,
248
+ },
249
+ },
250
+ ] as const;
251
+
252
+ /**
253
+ * Errors that mention limits, sizes, or tokens but are NOT fixed by
254
+ * compaction. The first two were captured live alongside the signatures
255
+ * above; the rest are the adjacent failures a loose matcher would swallow.
256
+ */
257
+ export const NON_OVERFLOW_SIGNATURES: readonly {
258
+ label: string;
259
+ error: Record<string, unknown>;
260
+ }[] = [
261
+ {
262
+ label: 'bedrock invalid model identifier (captured live)',
263
+ error: {
264
+ name: 'ValidationException',
265
+ message: 'The provided model identifier is invalid.',
266
+ $metadata: { httpStatusCode: 400 },
267
+ },
268
+ },
269
+ {
270
+ label: 'bedrock legacy model access denied (captured live)',
271
+ error: {
272
+ name: 'ResourceNotFoundException',
273
+ message:
274
+ 'Access denied. This Model is marked by provider as Legacy and you have not been actively using the model in the last 30 days. Please upgrade to an active model.',
275
+ $metadata: { httpStatusCode: 404 },
276
+ },
277
+ },
278
+ {
279
+ label: 'openai genuine token throttling — request alone fits the bucket',
280
+ error: {
281
+ name: 'Error',
282
+ status: 429,
283
+ code: 'rate_limit_exceeded',
284
+ type: 'tokens',
285
+ message:
286
+ '429 Rate limit reached for gpt-4o in organization org-test on tokens per min (TPM): Limit 30000, Used 28000, Requested 4000. Please try again in 4ms.',
287
+ },
288
+ },
289
+ {
290
+ label: 'openai request-per-minute throttling',
291
+ error: {
292
+ name: 'Error',
293
+ status: 429,
294
+ code: 'rate_limit_exceeded',
295
+ type: 'requests',
296
+ message:
297
+ '429 Rate limit reached for gpt-4o in organization org-test on requests per min (RPM): Limit 500, Used 500, Requested 1. Please try again in 120ms.',
298
+ },
299
+ },
300
+ {
301
+ label: 'output token cap, not input',
302
+ error: {
303
+ name: 'BadRequestError',
304
+ status: 400,
305
+ message:
306
+ '400 max_tokens is too large: 200000. This model supports at most 64000 completion tokens, whereas you provided 200000.',
307
+ },
308
+ },
309
+ {
310
+ label: 'anthropic max_tokens above model output limit',
311
+ error: {
312
+ name: 'BadRequestError',
313
+ status: 400,
314
+ message:
315
+ '400 {"type":"error","error":{"type":"invalid_request_error","message":"max_tokens: 100000 > 64000, which is the maximum allowed number of output tokens for claude-sonnet-4-5"}}',
316
+ },
317
+ },
318
+ {
319
+ label: 'quota exhausted',
320
+ error: {
321
+ name: 'Error',
322
+ status: 429,
323
+ code: 'insufficient_quota',
324
+ message:
325
+ '429 You exceeded your current quota, please check your plan and billing details.',
326
+ },
327
+ },
328
+ {
329
+ label: 'authentication failure',
330
+ error: {
331
+ name: 'AuthenticationError',
332
+ status: 401,
333
+ message: '401 Incorrect API key provided.',
334
+ },
335
+ },
336
+ ];
@@ -0,0 +1,56 @@
1
+ import { redactSecrets } from '@/utils/redactSecrets';
2
+
3
+ describe('redactSecrets', () => {
4
+ it('redacts secret-bearing keys at every nesting depth', () => {
5
+ const value = {
6
+ message: 'Bad Request',
7
+ config: {
8
+ headers: {
9
+ Authorization: 'Bearer live-secret',
10
+ 'x-api-key': 'live-key',
11
+ Accept: 'application/json',
12
+ },
13
+ },
14
+ response: {
15
+ data: [{ password: 'hunter2', detail: 'overflow' }],
16
+ },
17
+ };
18
+
19
+ const serialized = JSON.stringify(redactSecrets(value));
20
+
21
+ expect(serialized).toContain('Bad Request');
22
+ expect(serialized).toContain('application/json');
23
+ expect(serialized).toContain('overflow');
24
+ expect(serialized).not.toContain('live-secret');
25
+ expect(serialized).not.toContain('live-key');
26
+ expect(serialized).not.toContain('hunter2');
27
+ });
28
+
29
+ it('handles circular diagnostic payloads without exposing nested secrets', () => {
30
+ const value: { request?: object; token?: string } = {
31
+ token: 'live-token',
32
+ };
33
+ value.request = value;
34
+
35
+ expect(redactSecrets(value)).toEqual({
36
+ token: '[REDACTED]',
37
+ request: '[CIRCULAR]',
38
+ });
39
+ });
40
+
41
+ it('redacts credentials embedded in diagnostic URLs', () => {
42
+ const value = {
43
+ request: {
44
+ url: 'https://user:pass@example.com/path?api_key=live-key&X-Amz-Signature=live-signature&version=1',
45
+ },
46
+ };
47
+
48
+ const serialized = JSON.stringify(redactSecrets(value));
49
+
50
+ expect(serialized).toContain('version=1');
51
+ expect(serialized).not.toContain('live-key');
52
+ expect(serialized).not.toContain('live-signature');
53
+ expect(serialized).not.toContain('user');
54
+ expect(serialized).not.toContain('pass');
55
+ });
56
+ });