@librechat/agents 3.6.8 → 3.6.10

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 (158) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +46 -39
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/agents/projection.cjs +2 -2
  4. package/dist/cjs/agents/projection.cjs.map +1 -1
  5. package/dist/cjs/graphs/Graph.cjs +28 -18
  6. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  7. package/dist/cjs/graphs/MultiAgentGraph.cjs +87 -16
  8. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  9. package/dist/cjs/llm/openai/index.cjs +10 -0
  10. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  11. package/dist/cjs/main.cjs +14 -0
  12. package/dist/cjs/messages/alternation.cjs +125 -20
  13. package/dist/cjs/messages/alternation.cjs.map +1 -1
  14. package/dist/cjs/messages/core.cjs +195 -18
  15. package/dist/cjs/messages/core.cjs.map +1 -1
  16. package/dist/cjs/messages/format.cjs +488 -118
  17. package/dist/cjs/messages/format.cjs.map +1 -1
  18. package/dist/cjs/messages/handoffCue.cjs +3 -2
  19. package/dist/cjs/messages/handoffCue.cjs.map +1 -1
  20. package/dist/cjs/messages/index.cjs +1 -0
  21. package/dist/cjs/messages/injected.cjs +5 -2
  22. package/dist/cjs/messages/injected.cjs.map +1 -1
  23. package/dist/cjs/messages/provenance.cjs +411 -0
  24. package/dist/cjs/messages/provenance.cjs.map +1 -0
  25. package/dist/cjs/messages/prune.cjs +18 -13
  26. package/dist/cjs/messages/prune.cjs.map +1 -1
  27. package/dist/cjs/messages/toolResultTypes.cjs +663 -0
  28. package/dist/cjs/messages/toolResultTypes.cjs.map +1 -0
  29. package/dist/cjs/run.cjs +7 -4
  30. package/dist/cjs/run.cjs.map +1 -1
  31. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +21 -4
  32. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  33. package/dist/cjs/tools/CallerCapabilities.cjs +48 -0
  34. package/dist/cjs/tools/CallerCapabilities.cjs.map +1 -0
  35. package/dist/cjs/tools/ProgrammaticCallerPolicy.cjs +34 -0
  36. package/dist/cjs/tools/ProgrammaticCallerPolicy.cjs.map +1 -0
  37. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +21 -4
  38. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  39. package/dist/cjs/tools/ToolNode.cjs +25 -24
  40. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  41. package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs +12 -4
  42. package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs.map +1 -1
  43. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +13 -4
  44. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -1
  45. package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs +47 -11
  46. package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs.map +1 -1
  47. package/dist/cjs/tools/ptcTimeout.cjs.map +1 -1
  48. package/dist/cjs/tools/search/search.cjs +19 -9
  49. package/dist/cjs/tools/search/search.cjs.map +1 -1
  50. package/dist/cjs/tools/search/tool.cjs +2 -1
  51. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  52. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +1 -1
  53. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  54. package/dist/cjs/utils/toolContent.cjs +2 -2
  55. package/dist/cjs/utils/toolContent.cjs.map +1 -1
  56. package/dist/esm/agents/AgentContext.mjs +46 -39
  57. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  58. package/dist/esm/agents/projection.mjs +2 -2
  59. package/dist/esm/agents/projection.mjs.map +1 -1
  60. package/dist/esm/graphs/Graph.mjs +30 -20
  61. package/dist/esm/graphs/Graph.mjs.map +1 -1
  62. package/dist/esm/graphs/MultiAgentGraph.mjs +87 -16
  63. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  64. package/dist/esm/llm/openai/index.mjs +10 -0
  65. package/dist/esm/llm/openai/index.mjs.map +1 -1
  66. package/dist/esm/main.mjs +4 -3
  67. package/dist/esm/messages/alternation.mjs +125 -20
  68. package/dist/esm/messages/alternation.mjs.map +1 -1
  69. package/dist/esm/messages/core.mjs +196 -19
  70. package/dist/esm/messages/core.mjs.map +1 -1
  71. package/dist/esm/messages/format.mjs +488 -119
  72. package/dist/esm/messages/format.mjs.map +1 -1
  73. package/dist/esm/messages/handoffCue.mjs +3 -2
  74. package/dist/esm/messages/handoffCue.mjs.map +1 -1
  75. package/dist/esm/messages/index.mjs +1 -0
  76. package/dist/esm/messages/injected.mjs +5 -2
  77. package/dist/esm/messages/injected.mjs.map +1 -1
  78. package/dist/esm/messages/provenance.mjs +401 -0
  79. package/dist/esm/messages/provenance.mjs.map +1 -0
  80. package/dist/esm/messages/prune.mjs +18 -13
  81. package/dist/esm/messages/prune.mjs.map +1 -1
  82. package/dist/esm/messages/toolResultTypes.mjs +657 -0
  83. package/dist/esm/messages/toolResultTypes.mjs.map +1 -0
  84. package/dist/esm/run.mjs +7 -4
  85. package/dist/esm/run.mjs.map +1 -1
  86. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +21 -4
  87. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  88. package/dist/esm/tools/CallerCapabilities.mjs +45 -0
  89. package/dist/esm/tools/CallerCapabilities.mjs.map +1 -0
  90. package/dist/esm/tools/ProgrammaticCallerPolicy.mjs +32 -0
  91. package/dist/esm/tools/ProgrammaticCallerPolicy.mjs.map +1 -0
  92. package/dist/esm/tools/ProgrammaticToolCalling.mjs +21 -4
  93. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  94. package/dist/esm/tools/ToolNode.mjs +25 -24
  95. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  96. package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs +14 -6
  97. package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs.map +1 -1
  98. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +15 -6
  99. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -1
  100. package/dist/esm/tools/local/resolveLocalExecutionTools.mjs +47 -12
  101. package/dist/esm/tools/local/resolveLocalExecutionTools.mjs.map +1 -1
  102. package/dist/esm/tools/ptcTimeout.mjs.map +1 -1
  103. package/dist/esm/tools/search/search.mjs +19 -9
  104. package/dist/esm/tools/search/search.mjs.map +1 -1
  105. package/dist/esm/tools/search/tool.mjs +2 -1
  106. package/dist/esm/tools/search/tool.mjs.map +1 -1
  107. package/dist/esm/tools/subagent/SubagentExecutor.mjs +1 -1
  108. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  109. package/dist/esm/utils/toolContent.mjs +2 -2
  110. package/dist/esm/utils/toolContent.mjs.map +1 -1
  111. package/dist/types/agents/AgentContext.d.ts +8 -13
  112. package/dist/types/agents/projection.d.ts +3 -1
  113. package/dist/types/graphs/Graph.d.ts +1 -1
  114. package/dist/types/llm/openai/index.d.ts +20 -0
  115. package/dist/types/messages/format.d.ts +5 -0
  116. package/dist/types/messages/index.d.ts +1 -0
  117. package/dist/types/messages/provenance.d.ts +85 -0
  118. package/dist/types/messages/toolResultTypes.d.ts +33 -0
  119. package/dist/types/tools/CallerCapabilities.d.ts +12 -0
  120. package/dist/types/tools/ProgrammaticCallerPolicy.d.ts +20 -0
  121. package/dist/types/tools/ToolNode.d.ts +4 -7
  122. package/dist/types/tools/local/resolveLocalExecutionTools.d.ts +4 -0
  123. package/dist/types/tools/ptcTimeout.d.ts +8 -0
  124. package/dist/types/tools/search/types.d.ts +15 -0
  125. package/dist/types/types/graph.d.ts +3 -1
  126. package/dist/types/types/tools.d.ts +13 -2
  127. package/package.json +1 -1
  128. package/src/agents/AgentContext.ts +109 -67
  129. package/src/agents/projection.ts +9 -1
  130. package/src/graphs/Graph.ts +56 -23
  131. package/src/graphs/MultiAgentGraph.ts +175 -33
  132. package/src/llm/openai/index.ts +43 -0
  133. package/src/messages/alternation.ts +217 -34
  134. package/src/messages/core.ts +358 -38
  135. package/src/messages/format.ts +958 -137
  136. package/src/messages/handoffCue.ts +7 -4
  137. package/src/messages/index.ts +1 -0
  138. package/src/messages/injected.ts +9 -6
  139. package/src/messages/provenance.ts +897 -0
  140. package/src/messages/prune.ts +28 -10
  141. package/src/messages/toolResultTypes.ts +1174 -0
  142. package/src/run.ts +9 -4
  143. package/src/tools/BashProgrammaticToolCalling.ts +46 -12
  144. package/src/tools/CallerCapabilities.ts +78 -0
  145. package/src/tools/ProgrammaticCallerPolicy.ts +97 -0
  146. package/src/tools/ProgrammaticToolCalling.ts +41 -7
  147. package/src/tools/ToolNode.ts +70 -52
  148. package/src/tools/cloudflare/CloudflareProgrammaticToolCalling.ts +24 -26
  149. package/src/tools/local/LocalProgrammaticToolCalling.ts +28 -23
  150. package/src/tools/local/resolveLocalExecutionTools.ts +132 -13
  151. package/src/tools/ptcTimeout.ts +6 -0
  152. package/src/tools/search/search.ts +25 -8
  153. package/src/tools/search/tool.ts +2 -0
  154. package/src/tools/search/types.ts +16 -0
  155. package/src/tools/subagent/SubagentExecutor.ts +8 -4
  156. package/src/types/graph.ts +3 -0
  157. package/src/types/tools.ts +14 -3
  158. package/src/utils/toolContent.ts +2 -2
@@ -0,0 +1,897 @@
1
+ import { isProxy } from 'node:util/types';
2
+ import type { BaseMessage } from '@langchain/core/messages';
3
+
4
+ export const PROVIDER_MESSAGE_PROVENANCE_VERSION = 1 as const;
5
+
6
+ /** Recommended consumer trust bounds. Producers preserve complete lineage
7
+ * above these limits; security-sensitive consumers must reject oversized
8
+ * envelopes and take their fail-closed path rather than truncate attribution. */
9
+ export const PROVIDER_MESSAGE_PROVENANCE_LIMITS = Object.freeze({
10
+ maxParts: 256,
11
+ maxIndicesPerPart: 256,
12
+ maxTotalIndexRefs: 4_096,
13
+ maxSourceMessageIds: 256,
14
+ maxSourceMessageIdLength: 512,
15
+ maxSourceContentPartIndex: 4_095,
16
+ } as const);
17
+
18
+ /** Authorship of one logical contribution to a provider-bound message. */
19
+ export type ProviderMessageAttribution =
20
+ | 'user'
21
+ | 'model'
22
+ | 'tool'
23
+ | 'synthetic';
24
+
25
+ /**
26
+ * Lineage for one logical contribution to a provider-bound message.
27
+ * `sourceContentPartIndices` index the persisted source message's `content`
28
+ * array before formatting, filtering, or summary-boundary slicing.
29
+ */
30
+ export interface ProviderMessageProvenancePart {
31
+ readonly attribution: ProviderMessageAttribution;
32
+ readonly sourceMessageId?: string;
33
+ readonly sourceContentPartIndices?: readonly number[];
34
+ }
35
+
36
+ /** Stable, versioned provenance carried in `BaseMessage.additional_kwargs`. */
37
+ export interface ProviderMessageProvenance {
38
+ readonly version: typeof PROVIDER_MESSAGE_PROVENANCE_VERSION;
39
+ readonly parts: readonly ProviderMessageProvenancePart[];
40
+ }
41
+
42
+ /** Inert marker used only when a derived message must retain invalidity. */
43
+ export interface InvalidProviderMessageProvenance {
44
+ readonly version: typeof PROVIDER_MESSAGE_PROVENANCE_VERSION;
45
+ readonly parts: null;
46
+ }
47
+
48
+ /** Distinguishes absent metadata from an explicitly malformed envelope. */
49
+ export type ProviderMessageProvenanceState =
50
+ | { readonly status: 'absent' }
51
+ | { readonly status: 'invalid' }
52
+ | {
53
+ readonly status: 'valid';
54
+ readonly provenance: ProviderMessageProvenance;
55
+ };
56
+
57
+ /** Distinguishes absent lineage from validated ids and malformed metadata. */
58
+ export type ProviderSourceMessageIdsState =
59
+ | { readonly status: 'absent' }
60
+ | { readonly status: 'invalid' }
61
+ | {
62
+ readonly status: 'valid';
63
+ readonly sourceMessageIds: readonly string[];
64
+ };
65
+
66
+ /** Typed subset of `additional_kwargs` exposed at provider callbacks. */
67
+ export interface ProviderMessageProvenanceAdditionalKwargs {
68
+ readonly provenance?:
69
+ | ProviderMessageProvenance
70
+ | InvalidProviderMessageProvenance;
71
+ readonly sourceMessageId?: string;
72
+ readonly sourceMessageIds?: readonly string[];
73
+ }
74
+
75
+ interface UntrustedProviderMessageAdditionalKwargs
76
+ extends Record<string, unknown> {
77
+ provenance?: unknown;
78
+ sourceMessageId?: unknown;
79
+ sourceMessageIds?: unknown;
80
+ }
81
+
82
+ const PROVIDER_MESSAGE_ATTRIBUTIONS: ReadonlySet<ProviderMessageAttribution> =
83
+ new Set(['user', 'model', 'tool', 'synthetic']);
84
+ /** Only envelopes built from copied/frozen inputs by the setter enter this
85
+ * identity set. It avoids repeated O(n) canonicalization without trusting a
86
+ * message or caller-owned envelope identity. */
87
+ const immutableProviderMessageProvenance = new WeakSet<object>();
88
+ /** Plural source-id arrays minted by the setter are copied and frozen before
89
+ * entering this set. Their complete lineage may intentionally exceed the
90
+ * public trust bounds without forcing repeated validation on reads. */
91
+ const immutableProviderSourceMessageIds = new WeakSet<object>();
92
+ /** Binds the two immutable objects produced by one setter call. Independently
93
+ * valid envelopes from different messages must not be combined into a new
94
+ * oversized lineage that no setter ever published. */
95
+ const immutableProviderMessageSourceIds = new WeakMap<
96
+ object,
97
+ readonly string[]
98
+ >();
99
+ const absentProviderMessageProvenanceState = Object.freeze({
100
+ status: 'absent' as const,
101
+ });
102
+ const invalidProviderMessageProvenanceState = Object.freeze({
103
+ status: 'invalid' as const,
104
+ });
105
+ const absentProviderSourceMessageIdsState = Object.freeze({
106
+ status: 'absent' as const,
107
+ });
108
+ const invalidProviderSourceMessageIdsState = Object.freeze({
109
+ status: 'invalid' as const,
110
+ });
111
+ /** Fresh projections use this inert envelope to preserve explicit invalidity
112
+ * without retaining any hostile caller-owned object or array. */
113
+ const invalidProviderMessageProvenanceSentinel: InvalidProviderMessageProvenance =
114
+ Object.freeze({
115
+ version: PROVIDER_MESSAGE_PROVENANCE_VERSION,
116
+ parts: null,
117
+ });
118
+
119
+ function normalizeSourceMessageId(
120
+ candidate: unknown,
121
+ enforceTrustBounds = false
122
+ ): string | undefined {
123
+ if (typeof candidate !== 'string') {
124
+ return undefined;
125
+ }
126
+ if (
127
+ enforceTrustBounds &&
128
+ candidate.length >
129
+ PROVIDER_MESSAGE_PROVENANCE_LIMITS.maxSourceMessageIdLength
130
+ ) {
131
+ return undefined;
132
+ }
133
+ const normalized = candidate.trim();
134
+ return normalized.length > 0 ? normalized : undefined;
135
+ }
136
+
137
+ function appendSourceMessageId(
138
+ result: string[],
139
+ seen: Set<string>,
140
+ candidate: unknown,
141
+ enforceTrustBounds = false
142
+ ): boolean {
143
+ const sourceMessageId = normalizeSourceMessageId(
144
+ candidate,
145
+ enforceTrustBounds
146
+ );
147
+ if (sourceMessageId == null) {
148
+ return candidate === undefined;
149
+ }
150
+ if (seen.has(sourceMessageId)) {
151
+ return true;
152
+ }
153
+ seen.add(sourceMessageId);
154
+ result.push(sourceMessageId);
155
+ return true;
156
+ }
157
+
158
+ function normalizeSourceContentPartIndices(
159
+ indices: readonly number[] | undefined,
160
+ trustState?: { totalIndexRefs: number }
161
+ ): number[] | undefined {
162
+ if (indices == null) {
163
+ return undefined;
164
+ }
165
+ if (isProxy(indices) || !Array.isArray(indices)) {
166
+ throw new TypeError(
167
+ 'Provider source content part indices must be a non-empty array'
168
+ );
169
+ }
170
+ const length = indices.length;
171
+ if (!Number.isSafeInteger(length) || length === 0) {
172
+ throw new TypeError(
173
+ 'Provider source content part indices must be a non-empty array'
174
+ );
175
+ }
176
+ if (
177
+ trustState != null &&
178
+ (length > PROVIDER_MESSAGE_PROVENANCE_LIMITS.maxIndicesPerPart ||
179
+ trustState.totalIndexRefs + length >
180
+ PROVIDER_MESSAGE_PROVENANCE_LIMITS.maxTotalIndexRefs)
181
+ ) {
182
+ throw new TypeError('Provider source content part indices exceed limits');
183
+ }
184
+ if (trustState != null) {
185
+ trustState.totalIndexRefs += length;
186
+ }
187
+ const normalized: number[] = [];
188
+ const seen = new Set<number>();
189
+ for (let position = 0; position < length; position++) {
190
+ const index = indices[position];
191
+ if (
192
+ !Number.isSafeInteger(index) ||
193
+ index < 0 ||
194
+ (trustState != null &&
195
+ index > PROVIDER_MESSAGE_PROVENANCE_LIMITS.maxSourceContentPartIndex)
196
+ ) {
197
+ throw new TypeError('Invalid provider source content part index');
198
+ }
199
+ if (seen.has(index)) {
200
+ continue;
201
+ }
202
+ seen.add(index);
203
+ normalized.push(index);
204
+ }
205
+ return normalized.length > 0 ? normalized : undefined;
206
+ }
207
+
208
+ function normalizeProvenancePart(
209
+ part: ProviderMessageProvenancePart | null | undefined,
210
+ requireCanonicalSourceMessageId = false,
211
+ trustState?: { totalIndexRefs: number; sourceMessageIdRefs: number }
212
+ ): ProviderMessageProvenancePart {
213
+ if (part == null || typeof part !== 'object') {
214
+ throw new TypeError('Invalid provider message provenance attribution');
215
+ }
216
+ /** Capture each potentially accessor-backed public input exactly once before
217
+ * validation so a changing getter cannot pass one value and publish another. */
218
+ const attribution = part.attribution;
219
+ const sourceMessageIdInput = part.sourceMessageId;
220
+ const sourceContentPartIndicesInput = part.sourceContentPartIndices;
221
+ if (!PROVIDER_MESSAGE_ATTRIBUTIONS.has(attribution)) {
222
+ throw new TypeError('Invalid provider message provenance attribution');
223
+ }
224
+ const sourceMessageId = normalizeSourceMessageId(
225
+ sourceMessageIdInput,
226
+ trustState != null
227
+ );
228
+ if (
229
+ sourceMessageIdInput !== undefined &&
230
+ (sourceMessageId == null ||
231
+ (requireCanonicalSourceMessageId &&
232
+ sourceMessageId !== sourceMessageIdInput))
233
+ ) {
234
+ throw new TypeError('Invalid provider source message id');
235
+ }
236
+ if (sourceMessageId != null && trustState != null) {
237
+ trustState.sourceMessageIdRefs++;
238
+ if (
239
+ trustState.sourceMessageIdRefs >
240
+ PROVIDER_MESSAGE_PROVENANCE_LIMITS.maxSourceMessageIds
241
+ ) {
242
+ throw new TypeError('Provider source message ids exceed limits');
243
+ }
244
+ }
245
+ const sourceContentPartIndices = normalizeSourceContentPartIndices(
246
+ sourceContentPartIndicesInput,
247
+ trustState
248
+ );
249
+ return Object.freeze({
250
+ attribution,
251
+ ...(sourceMessageId != null && { sourceMessageId }),
252
+ ...(sourceContentPartIndices != null && {
253
+ sourceContentPartIndices: Object.freeze(sourceContentPartIndices),
254
+ }),
255
+ });
256
+ }
257
+
258
+ function normalizeProvenanceParts(
259
+ parts: unknown,
260
+ requireCanonicalSourceMessageId = false,
261
+ enforceTrustBounds = false
262
+ ): readonly ProviderMessageProvenancePart[] {
263
+ if (isProxy(parts) || !Array.isArray(parts)) {
264
+ throw new TypeError('Provider message provenance parts must be an array');
265
+ }
266
+ const length = parts.length;
267
+ if (!Number.isSafeInteger(length) || length === 0) {
268
+ throw new TypeError('Provider message provenance parts cannot be empty');
269
+ }
270
+ if (
271
+ enforceTrustBounds &&
272
+ length > PROVIDER_MESSAGE_PROVENANCE_LIMITS.maxParts
273
+ ) {
274
+ throw new TypeError('Provider message provenance parts exceed limits');
275
+ }
276
+ const trustState = enforceTrustBounds
277
+ ? { totalIndexRefs: 0, sourceMessageIdRefs: 0 }
278
+ : undefined;
279
+ const normalizedParts: ProviderMessageProvenancePart[] = [];
280
+ for (let index = 0; index < length; index++) {
281
+ const part = parts[index] as
282
+ | ProviderMessageProvenancePart
283
+ | null
284
+ | undefined;
285
+ normalizedParts.push(
286
+ normalizeProvenancePart(part, requireCanonicalSourceMessageId, trustState)
287
+ );
288
+ }
289
+ return Object.freeze(normalizedParts);
290
+ }
291
+
292
+ function normalizeProviderMessageProvenance(
293
+ provenanceInput: unknown
294
+ ): ProviderMessageProvenance | undefined {
295
+ try {
296
+ if (provenanceInput == null || typeof provenanceInput !== 'object') {
297
+ return undefined;
298
+ }
299
+ if (immutableProviderMessageProvenance.has(provenanceInput)) {
300
+ return provenanceInput as ProviderMessageProvenance;
301
+ }
302
+ const provenance = provenanceInput as {
303
+ version?: unknown;
304
+ parts?: unknown;
305
+ };
306
+ /** Capture accessor-backed envelope fields exactly once, then publish only
307
+ * a canonical immutable copy rather than returning the untrusted object. */
308
+ const version = provenance.version;
309
+ const partsInput = provenance.parts;
310
+ if (version !== PROVIDER_MESSAGE_PROVENANCE_VERSION) {
311
+ return undefined;
312
+ }
313
+ const parts = normalizeProvenanceParts(partsInput, true, true);
314
+ return Object.freeze({
315
+ version: PROVIDER_MESSAGE_PROVENANCE_VERSION,
316
+ parts,
317
+ });
318
+ } catch {
319
+ return undefined;
320
+ }
321
+ }
322
+
323
+ function getUntrustedAdditionalKwargs(
324
+ message: BaseMessage
325
+ ): UntrustedProviderMessageAdditionalKwargs | undefined {
326
+ try {
327
+ const additionalKwargs: unknown = message.additional_kwargs;
328
+ return additionalKwargs != null && typeof additionalKwargs === 'object'
329
+ ? (additionalKwargs as UntrustedProviderMessageAdditionalKwargs)
330
+ : undefined;
331
+ } catch {
332
+ return undefined;
333
+ }
334
+ }
335
+
336
+ function readAdditionalKwarg(
337
+ additionalKwargs: UntrustedProviderMessageAdditionalKwargs | undefined,
338
+ key: keyof UntrustedProviderMessageAdditionalKwargs
339
+ ): unknown {
340
+ try {
341
+ return additionalKwargs?.[key];
342
+ } catch {
343
+ return undefined;
344
+ }
345
+ }
346
+
347
+ function collectProviderSourceMessageIds(
348
+ provenance: ProviderMessageProvenance | undefined,
349
+ pluralInput: unknown,
350
+ singularInput: unknown
351
+ ): string[] | undefined {
352
+ const result: string[] = [];
353
+ const seen = new Set<string>();
354
+ const trustedProvenance =
355
+ provenance != null && immutableProviderMessageProvenance.has(provenance);
356
+ const boundPlural = trustedProvenance
357
+ ? immutableProviderMessageSourceIds.get(provenance!)
358
+ : undefined;
359
+ let hasUntrustedSourceMetadata = provenance != null && !trustedProvenance;
360
+ let trustedPlural = false;
361
+ const provenanceParts = provenance?.parts;
362
+
363
+ /** Resolve trust across the setter-minted envelope/id pair before walking
364
+ * either collection. A trusted object spliced from another setter call is
365
+ * no longer a trusted combined lineage, so the public bounds apply to both
366
+ * sides and oversized inputs fail before any per-item work. */
367
+ let plural: readonly unknown[] | undefined;
368
+ let pluralLength = 0;
369
+ try {
370
+ if (pluralInput !== undefined) {
371
+ if (isProxy(pluralInput) || !Array.isArray(pluralInput)) {
372
+ return undefined;
373
+ }
374
+ plural = pluralInput;
375
+ pluralLength = pluralInput.length;
376
+ if (!Number.isSafeInteger(pluralLength) || pluralLength === 0) {
377
+ return undefined;
378
+ }
379
+ trustedPlural = immutableProviderSourceMessageIds.has(pluralInput);
380
+ const bindingMismatch = trustedProvenance && boundPlural !== pluralInput;
381
+ if (bindingMismatch) {
382
+ trustedPlural = false;
383
+ }
384
+ if (!trustedPlural) {
385
+ hasUntrustedSourceMetadata = true;
386
+ }
387
+ }
388
+ } catch {
389
+ return undefined;
390
+ }
391
+
392
+ /** A primitive singular id has no identity of its own. Treat it as the
393
+ * setter's compatibility duplicate only when it exactly matches the last id
394
+ * of an immutable plural array published by that setter. This classification
395
+ * happens before either collection is walked so malformed singular metadata
396
+ * cannot force work over an otherwise trusted oversized lineage. */
397
+ const trustedSingularSourceIds =
398
+ boundPlural ?? (trustedPlural ? plural : undefined);
399
+ let trustedSingularSourceIdsLength = 0;
400
+ if (trustedSingularSourceIds != null) {
401
+ trustedSingularSourceIdsLength =
402
+ trustedSingularSourceIds === plural
403
+ ? pluralLength
404
+ : trustedSingularSourceIds.length;
405
+ }
406
+ const unboundedSingular =
407
+ singularInput === undefined
408
+ ? undefined
409
+ : normalizeSourceMessageId(singularInput);
410
+ const trustedSingularDuplicate =
411
+ singularInput !== undefined &&
412
+ typeof singularInput === 'string' &&
413
+ singularInput === unboundedSingular &&
414
+ trustedSingularSourceIds != null &&
415
+ trustedSingularSourceIdsLength > 0 &&
416
+ trustedSingularSourceIds[trustedSingularSourceIdsLength - 1] ===
417
+ singularInput;
418
+ if (singularInput !== undefined && !trustedSingularDuplicate) {
419
+ hasUntrustedSourceMetadata = true;
420
+ if (normalizeSourceMessageId(singularInput, true) == null) {
421
+ return undefined;
422
+ }
423
+ }
424
+
425
+ /** Any untrusted lineage field revokes the setter-only size exemption for
426
+ * every collection participating in the union. Preflight all public bounds
427
+ * before reading a part or plural element. */
428
+ if (hasUntrustedSourceMetadata) {
429
+ try {
430
+ if (
431
+ provenanceParts != null &&
432
+ provenanceParts.length > PROVIDER_MESSAGE_PROVENANCE_LIMITS.maxParts
433
+ ) {
434
+ return undefined;
435
+ }
436
+ if (
437
+ plural != null &&
438
+ pluralLength > PROVIDER_MESSAGE_PROVENANCE_LIMITS.maxSourceMessageIds
439
+ ) {
440
+ return undefined;
441
+ }
442
+ } catch {
443
+ return undefined;
444
+ }
445
+ }
446
+ const provenanceRequiresValidation =
447
+ !trustedProvenance || hasUntrustedSourceMetadata;
448
+ const pluralRequiresValidation = !trustedPlural || hasUntrustedSourceMetadata;
449
+
450
+ if (provenanceParts != null) {
451
+ const length = provenanceParts.length;
452
+ for (let index = 0; index < length; index++) {
453
+ if (
454
+ !appendSourceMessageId(
455
+ result,
456
+ seen,
457
+ provenanceParts[index].sourceMessageId,
458
+ provenanceRequiresValidation
459
+ )
460
+ ) {
461
+ return undefined;
462
+ }
463
+ }
464
+ }
465
+
466
+ try {
467
+ if (plural != null) {
468
+ for (let index = 0; index < pluralLength; index++) {
469
+ if (
470
+ !appendSourceMessageId(
471
+ result,
472
+ seen,
473
+ plural[index],
474
+ pluralRequiresValidation
475
+ )
476
+ ) {
477
+ return undefined;
478
+ }
479
+ }
480
+ }
481
+ } catch {
482
+ return undefined;
483
+ }
484
+ if (singularInput !== undefined) {
485
+ if (trustedSingularDuplicate) {
486
+ if (!seen.has(singularInput)) {
487
+ return undefined;
488
+ }
489
+ } else {
490
+ if (!appendSourceMessageId(result, seen, singularInput, true)) {
491
+ return undefined;
492
+ }
493
+ }
494
+ }
495
+ if (
496
+ hasUntrustedSourceMetadata &&
497
+ result.length > PROVIDER_MESSAGE_PROVENANCE_LIMITS.maxSourceMessageIds
498
+ ) {
499
+ return undefined;
500
+ }
501
+ return result;
502
+ }
503
+
504
+ function copyOwnEnumerableDataProperties(
505
+ input: object
506
+ ): Record<PropertyKey, unknown> {
507
+ if (isProxy(input)) {
508
+ throw new TypeError('Invalid provider message serialization kwargs');
509
+ }
510
+ const result: Record<PropertyKey, unknown> = {};
511
+ const descriptors = Object.getOwnPropertyDescriptors(input);
512
+ for (const key of Reflect.ownKeys(descriptors)) {
513
+ const descriptor = Reflect.get(descriptors, key) as PropertyDescriptor;
514
+ if (descriptor.enumerable !== true) {
515
+ continue;
516
+ }
517
+ if (!('value' in descriptor)) {
518
+ throw new TypeError('Invalid provider message serialization kwargs');
519
+ }
520
+ Object.defineProperty(result, key, {
521
+ configurable: true,
522
+ enumerable: true,
523
+ value: descriptor.value,
524
+ writable: true,
525
+ });
526
+ }
527
+ return result;
528
+ }
529
+
530
+ /** Legacy Human rows are treated as user-authored when no typed metadata is
531
+ * available. Unknown custom message types take the same conservative path. */
532
+ function inferLegacyMessageAttribution(
533
+ message: BaseMessage
534
+ ): ProviderMessageAttribution {
535
+ const messageType = message.type;
536
+ if (messageType === 'ai') {
537
+ return 'model';
538
+ }
539
+ if (messageType === 'tool') {
540
+ return 'tool';
541
+ }
542
+ if (messageType === 'system') {
543
+ return 'synthetic';
544
+ }
545
+ return 'user';
546
+ }
547
+
548
+ /** Returns explicitly stamped provenance without inferring from message role. */
549
+ export function getProviderMessageProvenance(
550
+ message: BaseMessage
551
+ ): ProviderMessageProvenance | undefined {
552
+ const additionalKwargs = getUntrustedAdditionalKwargs(message);
553
+ const provenanceInput = readAdditionalKwarg(additionalKwargs, 'provenance');
554
+ return normalizeProviderMessageProvenance(provenanceInput);
555
+ }
556
+
557
+ /** Safely preserves the semantic difference between absent and invalid input. */
558
+ export function inspectProviderMessageProvenance(
559
+ message: BaseMessage
560
+ ): ProviderMessageProvenanceState {
561
+ let additionalKwargs: unknown;
562
+ try {
563
+ additionalKwargs = message.additional_kwargs;
564
+ } catch {
565
+ return invalidProviderMessageProvenanceState;
566
+ }
567
+ if (additionalKwargs == null || typeof additionalKwargs !== 'object') {
568
+ return absentProviderMessageProvenanceState;
569
+ }
570
+ let provenanceInput: unknown;
571
+ try {
572
+ provenanceInput = (additionalKwargs as { provenance?: unknown })
573
+ .provenance;
574
+ } catch {
575
+ return invalidProviderMessageProvenanceState;
576
+ }
577
+ if (provenanceInput == null) {
578
+ return absentProviderMessageProvenanceState;
579
+ }
580
+ const provenance = normalizeProviderMessageProvenance(provenanceInput);
581
+ return provenance == null
582
+ ? invalidProviderMessageProvenanceState
583
+ : { status: 'valid', provenance };
584
+ }
585
+
586
+ function readProviderSourceMessageIds(
587
+ message: BaseMessage
588
+ ): string[] | null | undefined {
589
+ let additionalKwargs: unknown;
590
+ try {
591
+ additionalKwargs = message.additional_kwargs;
592
+ } catch {
593
+ return null;
594
+ }
595
+ if (additionalKwargs == null || typeof additionalKwargs !== 'object') {
596
+ return undefined;
597
+ }
598
+ if (isProxy(additionalKwargs)) {
599
+ return null;
600
+ }
601
+ let provenanceInput: unknown;
602
+ let pluralInput: unknown;
603
+ let singularInput: unknown;
604
+ try {
605
+ const sourceMetadata =
606
+ additionalKwargs as UntrustedProviderMessageAdditionalKwargs;
607
+ provenanceInput = sourceMetadata.provenance;
608
+ pluralInput = sourceMetadata.sourceMessageIds;
609
+ singularInput = sourceMetadata.sourceMessageId;
610
+ } catch {
611
+ return null;
612
+ }
613
+ const hasProvenanceInput = provenanceInput != null;
614
+ const hasLegacyInput =
615
+ pluralInput !== undefined || singularInput !== undefined;
616
+ if (!hasProvenanceInput && !hasLegacyInput) {
617
+ return undefined;
618
+ }
619
+ const provenance = normalizeProviderMessageProvenance(provenanceInput);
620
+ if (hasProvenanceInput && provenance == null) {
621
+ return null;
622
+ }
623
+ return (
624
+ collectProviderSourceMessageIds(
625
+ provenance,
626
+ pluralInput,
627
+ singularInput
628
+ ) ?? null
629
+ );
630
+ }
631
+
632
+ /** Safely distinguishes missing source lineage from malformed metadata. */
633
+ export function inspectProviderSourceMessageIds(
634
+ message: BaseMessage
635
+ ): ProviderSourceMessageIdsState {
636
+ const sourceMessageIds = readProviderSourceMessageIds(message);
637
+ if (sourceMessageIds === undefined) {
638
+ return absentProviderSourceMessageIdsState;
639
+ }
640
+ if (sourceMessageIds === null) {
641
+ return invalidProviderSourceMessageIdsState;
642
+ }
643
+ return { status: 'valid', sourceMessageIds };
644
+ }
645
+
646
+ /** True when indexed contributions uniquely cover every current content part. */
647
+ export function hasBijectiveProviderContentPartMapping(
648
+ parts: readonly ProviderMessageProvenancePart[],
649
+ contentPartCount: number
650
+ ): boolean {
651
+ if (!Number.isSafeInteger(contentPartCount) || contentPartCount <= 0) {
652
+ return false;
653
+ }
654
+ const seen = new Set<number>();
655
+ for (const part of parts) {
656
+ const indices = part.sourceContentPartIndices;
657
+ if (indices == null) {
658
+ return false;
659
+ }
660
+ for (const index of indices) {
661
+ if (
662
+ !Number.isSafeInteger(index) ||
663
+ index < 0 ||
664
+ index >= contentPartCount ||
665
+ seen.has(index)
666
+ ) {
667
+ return false;
668
+ }
669
+ seen.add(index);
670
+ }
671
+ }
672
+ return seen.size === contentPartCount;
673
+ }
674
+
675
+ /**
676
+ * Returns every explicit persisted source id in stable content order.
677
+ * Typed parts, plural lineage, and the legacy singular id are unioned in that
678
+ * precedence order; duplicates are removed without reordering.
679
+ */
680
+ export function getProviderSourceMessageIds(message: BaseMessage): string[] {
681
+ const additionalKwargs = getUntrustedAdditionalKwargs(message);
682
+ const provenanceInput = readAdditionalKwarg(additionalKwargs, 'provenance');
683
+ const pluralInput = readAdditionalKwarg(additionalKwargs, 'sourceMessageIds');
684
+ const singularInput = readAdditionalKwarg(
685
+ additionalKwargs,
686
+ 'sourceMessageId'
687
+ );
688
+ return (
689
+ collectProviderSourceMessageIds(
690
+ normalizeProviderMessageProvenance(provenanceInput),
691
+ pluralInput,
692
+ singularInput
693
+ ) ?? []
694
+ );
695
+ }
696
+
697
+ function publishProviderMessageProvenance(
698
+ message: BaseMessage,
699
+ provenance: unknown,
700
+ sourceMessageIds?: readonly string[]
701
+ ): void {
702
+ if (isProxy(message)) {
703
+ throw new TypeError('Invalid provider message serialization kwargs');
704
+ }
705
+ const liveDescriptor = Object.getOwnPropertyDescriptor(
706
+ message,
707
+ 'additional_kwargs'
708
+ );
709
+ const lcKwargsDescriptor = Object.getOwnPropertyDescriptor(
710
+ message,
711
+ 'lc_kwargs'
712
+ );
713
+ if (
714
+ liveDescriptor == null ||
715
+ !('value' in liveDescriptor) ||
716
+ liveDescriptor.writable !== true ||
717
+ lcKwargsDescriptor == null ||
718
+ !('value' in lcKwargsDescriptor) ||
719
+ lcKwargsDescriptor.writable !== true
720
+ ) {
721
+ throw new TypeError('Invalid provider message serialization kwargs');
722
+ }
723
+ const currentAdditionalKwargsInput: unknown = liveDescriptor.value;
724
+ if (
725
+ currentAdditionalKwargsInput == null ||
726
+ typeof currentAdditionalKwargsInput !== 'object'
727
+ ) {
728
+ throw new TypeError('Invalid provider message additional kwargs');
729
+ }
730
+ let replacement: UntrustedProviderMessageAdditionalKwargs;
731
+ try {
732
+ replacement = copyOwnEnumerableDataProperties(
733
+ currentAdditionalKwargsInput
734
+ ) as UntrustedProviderMessageAdditionalKwargs;
735
+ } catch {
736
+ throw new TypeError('Invalid provider message additional kwargs');
737
+ }
738
+ const lcKwargsInput: unknown = lcKwargsDescriptor.value;
739
+ if (lcKwargsInput == null || typeof lcKwargsInput !== 'object') {
740
+ throw new TypeError('Invalid provider message serialization kwargs');
741
+ }
742
+ let serializedReplacement: Record<PropertyKey, unknown>;
743
+ try {
744
+ serializedReplacement = copyOwnEnumerableDataProperties(lcKwargsInput);
745
+ } catch {
746
+ throw new TypeError('Invalid provider message serialization kwargs');
747
+ }
748
+ replacement.provenance = provenance;
749
+ if (sourceMessageIds != null && sourceMessageIds.length > 0) {
750
+ replacement.sourceMessageIds = sourceMessageIds;
751
+ replacement.sourceMessageId = sourceMessageIds[sourceMessageIds.length - 1];
752
+ } else {
753
+ delete replacement.sourceMessageIds;
754
+ delete replacement.sourceMessageId;
755
+ }
756
+ serializedReplacement.additional_kwargs = replacement;
757
+ try {
758
+ /** Both own data properties are prevalidated before one descriptor batch,
759
+ * so custom accessors cannot observe or create a split publication. */
760
+ Object.defineProperties(message, {
761
+ additional_kwargs: { ...liveDescriptor, value: replacement },
762
+ lc_kwargs: { ...lcKwargsDescriptor, value: serializedReplacement },
763
+ });
764
+ } catch {
765
+ throw new TypeError('Invalid provider message serialization kwargs');
766
+ }
767
+ }
768
+
769
+ /** Replaces typed provenance and synchronizes its stable plural source ids. */
770
+ export function setProviderMessageProvenance(
771
+ message: BaseMessage,
772
+ parts: readonly ProviderMessageProvenancePart[]
773
+ ): void {
774
+ const normalizedParts = normalizeProvenanceParts(parts);
775
+ const provenance: ProviderMessageProvenance = Object.freeze({
776
+ version: PROVIDER_MESSAGE_PROVENANCE_VERSION,
777
+ parts: normalizedParts,
778
+ });
779
+ immutableProviderMessageProvenance.add(provenance);
780
+ const sourceMessageIds: string[] = [];
781
+ const seen = new Set<string>();
782
+ for (const part of normalizedParts) {
783
+ appendSourceMessageId(sourceMessageIds, seen, part.sourceMessageId);
784
+ }
785
+ if (sourceMessageIds.length > 0) {
786
+ const immutableSourceMessageIds = Object.freeze(sourceMessageIds);
787
+ immutableProviderSourceMessageIds.add(immutableSourceMessageIds);
788
+ immutableProviderMessageSourceIds.set(
789
+ provenance,
790
+ immutableSourceMessageIds
791
+ );
792
+ publishProviderMessageProvenance(
793
+ message,
794
+ provenance,
795
+ immutableSourceMessageIds
796
+ );
797
+ return;
798
+ }
799
+ publishProviderMessageProvenance(message, provenance);
800
+ }
801
+
802
+ /** Publishes the canonical fail-closed marker for malformed provenance. */
803
+ export function setInvalidProviderMessageProvenance(
804
+ message: BaseMessage
805
+ ): void {
806
+ publishProviderMessageProvenance(
807
+ message,
808
+ invalidProviderMessageProvenanceSentinel
809
+ );
810
+ }
811
+
812
+ /** Marks a provider-visible runtime message as host-generated context. */
813
+ export function stampSyntheticProviderMessage<T extends BaseMessage>(
814
+ message: T
815
+ ): T {
816
+ setProviderMessageProvenance(message, [{ attribution: 'synthetic' }]);
817
+ return message;
818
+ }
819
+
820
+ /**
821
+ * Adds one logical lineage contribution, migrating legacy ids on first use.
822
+ * This compatibility helper rebuilds validated public metadata; hot formatters
823
+ * should accumulate locally and call `setProviderMessageProvenance` once.
824
+ */
825
+ export function appendProviderMessageProvenance(
826
+ message: BaseMessage,
827
+ part: ProviderMessageProvenancePart
828
+ ): void {
829
+ let normalizedPart = normalizeProvenancePart(part);
830
+ const additionalKwargs = getUntrustedAdditionalKwargs(message);
831
+ const provenanceInput = readAdditionalKwarg(additionalKwargs, 'provenance');
832
+ const pluralInput = readAdditionalKwarg(additionalKwargs, 'sourceMessageIds');
833
+ const singularInput = readAdditionalKwarg(
834
+ additionalKwargs,
835
+ 'sourceMessageId'
836
+ );
837
+ const provenance = normalizeProviderMessageProvenance(provenanceInput);
838
+ const existing = provenance?.parts ?? [];
839
+ const representedSourceIds = new Set<string>();
840
+ for (const existingPart of existing) {
841
+ if (existingPart.sourceMessageId != null) {
842
+ representedSourceIds.add(existingPart.sourceMessageId);
843
+ }
844
+ }
845
+ const sourceMessageIds =
846
+ collectProviderSourceMessageIds(provenance, pluralInput, singularInput) ??
847
+ [];
848
+ const missingLegacySourceIds = sourceMessageIds.filter(
849
+ (sourceMessageId) => !representedSourceIds.has(sourceMessageId)
850
+ );
851
+ const migratedParts: ProviderMessageProvenancePart[] = [...existing];
852
+ const legacyAttribution = inferLegacyMessageAttribution(message);
853
+ for (const sourceMessageId of missingLegacySourceIds) {
854
+ migratedParts.push({
855
+ attribution: legacyAttribution,
856
+ sourceMessageId,
857
+ });
858
+ }
859
+ if (normalizedPart.sourceMessageId == null && sourceMessageIds.length === 1) {
860
+ normalizedPart = {
861
+ ...normalizedPart,
862
+ sourceMessageId: sourceMessageIds[0],
863
+ };
864
+ const migratedIndex = migratedParts.findIndex(
865
+ (migratedPart, index) =>
866
+ index >= existing.length &&
867
+ migratedPart.attribution === normalizedPart.attribution &&
868
+ migratedPart.sourceMessageId === sourceMessageIds[0]
869
+ );
870
+ if (migratedIndex >= 0) {
871
+ migratedParts.splice(migratedIndex, 1);
872
+ }
873
+ }
874
+ const lastPart = migratedParts[migratedParts.length - 1];
875
+ if (
876
+ migratedParts.length > 0 &&
877
+ lastPart.attribution === normalizedPart.attribution &&
878
+ lastPart.sourceMessageId === normalizedPart.sourceMessageId &&
879
+ (lastPart.sourceContentPartIndices == null) ===
880
+ (normalizedPart.sourceContentPartIndices == null)
881
+ ) {
882
+ const sourceContentPartIndices = normalizeSourceContentPartIndices([
883
+ ...(lastPart.sourceContentPartIndices ?? []),
884
+ ...(normalizedPart.sourceContentPartIndices ?? []),
885
+ ]);
886
+ migratedParts[migratedParts.length - 1] = {
887
+ attribution: lastPart.attribution,
888
+ ...(lastPart.sourceMessageId != null && {
889
+ sourceMessageId: lastPart.sourceMessageId,
890
+ }),
891
+ ...(sourceContentPartIndices != null && { sourceContentPartIndices }),
892
+ };
893
+ } else {
894
+ migratedParts.push(normalizedPart);
895
+ }
896
+ setProviderMessageProvenance(message, migratedParts);
897
+ }