@librechat/agents 3.9.3 → 3.9.5

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/README.md +32 -0
  2. package/dist/cjs/common/enum.cjs +2 -0
  3. package/dist/cjs/common/enum.cjs.map +1 -1
  4. package/dist/cjs/events.cjs +11 -0
  5. package/dist/cjs/events.cjs.map +1 -1
  6. package/dist/cjs/graphs/Graph.cjs +41 -1
  7. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  8. package/dist/cjs/graphs/acceptedModelResponse.cjs +168 -0
  9. package/dist/cjs/graphs/acceptedModelResponse.cjs.map +1 -0
  10. package/dist/cjs/llm/invoke.cjs +10 -5
  11. package/dist/cjs/llm/invoke.cjs.map +1 -1
  12. package/dist/cjs/llm/streamLimits.cjs +1 -1
  13. package/dist/cjs/llm/streamLimits.cjs.map +1 -1
  14. package/dist/cjs/main.cjs +2 -0
  15. package/dist/cjs/messages/fading.cjs +14 -6
  16. package/dist/cjs/messages/fading.cjs.map +1 -1
  17. package/dist/cjs/messages/prune.cjs +95 -36
  18. package/dist/cjs/messages/prune.cjs.map +1 -1
  19. package/dist/cjs/openai/index.cjs +2 -0
  20. package/dist/cjs/openai/index.cjs.map +1 -1
  21. package/dist/cjs/openai/toolProjection.cjs +196 -0
  22. package/dist/cjs/openai/toolProjection.cjs.map +1 -0
  23. package/dist/cjs/run.cjs +8 -1
  24. package/dist/cjs/run.cjs.map +1 -1
  25. package/dist/cjs/session/AgentSession.cjs +1 -1
  26. package/dist/cjs/session/AgentSession.cjs.map +1 -1
  27. package/dist/cjs/stream.cjs +9 -4
  28. package/dist/cjs/stream.cjs.map +1 -1
  29. package/dist/cjs/tools/ArtifactTruncation.cjs +45 -0
  30. package/dist/cjs/tools/ArtifactTruncation.cjs.map +1 -0
  31. package/dist/cjs/tools/BashExecutor.cjs +6 -1
  32. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  33. package/dist/cjs/tools/CodeExecutor.cjs +6 -1
  34. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  35. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +5 -1
  36. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  37. package/dist/cjs/tools/ToolNode.cjs +2 -1
  38. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  39. package/dist/cjs/tools/subagent/SubagentReplay.cjs +4 -1
  40. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
  41. package/dist/cjs/utils/acceptedToolArguments.cjs +143 -0
  42. package/dist/cjs/utils/acceptedToolArguments.cjs.map +1 -0
  43. package/dist/esm/common/enum.mjs +2 -0
  44. package/dist/esm/common/enum.mjs.map +1 -1
  45. package/dist/esm/events.mjs +11 -0
  46. package/dist/esm/events.mjs.map +1 -1
  47. package/dist/esm/graphs/Graph.mjs +41 -1
  48. package/dist/esm/graphs/Graph.mjs.map +1 -1
  49. package/dist/esm/graphs/acceptedModelResponse.mjs +165 -0
  50. package/dist/esm/graphs/acceptedModelResponse.mjs.map +1 -0
  51. package/dist/esm/llm/invoke.mjs +10 -5
  52. package/dist/esm/llm/invoke.mjs.map +1 -1
  53. package/dist/esm/llm/streamLimits.mjs +1 -1
  54. package/dist/esm/llm/streamLimits.mjs.map +1 -1
  55. package/dist/esm/main.mjs +3 -3
  56. package/dist/esm/messages/fading.mjs +14 -7
  57. package/dist/esm/messages/fading.mjs.map +1 -1
  58. package/dist/esm/messages/prune.mjs +95 -37
  59. package/dist/esm/messages/prune.mjs.map +1 -1
  60. package/dist/esm/openai/index.mjs +2 -1
  61. package/dist/esm/openai/index.mjs.map +1 -1
  62. package/dist/esm/openai/toolProjection.mjs +196 -0
  63. package/dist/esm/openai/toolProjection.mjs.map +1 -0
  64. package/dist/esm/run.mjs +8 -1
  65. package/dist/esm/run.mjs.map +1 -1
  66. package/dist/esm/session/AgentSession.mjs +1 -1
  67. package/dist/esm/session/AgentSession.mjs.map +1 -1
  68. package/dist/esm/stream.mjs +9 -4
  69. package/dist/esm/stream.mjs.map +1 -1
  70. package/dist/esm/tools/ArtifactTruncation.mjs +44 -0
  71. package/dist/esm/tools/ArtifactTruncation.mjs.map +1 -0
  72. package/dist/esm/tools/BashExecutor.mjs +6 -1
  73. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  74. package/dist/esm/tools/CodeExecutor.mjs +6 -1
  75. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  76. package/dist/esm/tools/ProgrammaticToolCalling.mjs +5 -1
  77. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  78. package/dist/esm/tools/ToolNode.mjs +2 -1
  79. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  80. package/dist/esm/tools/subagent/SubagentReplay.mjs +5 -2
  81. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
  82. package/dist/esm/utils/acceptedToolArguments.mjs +142 -0
  83. package/dist/esm/utils/acceptedToolArguments.mjs.map +1 -0
  84. package/dist/types/common/enum.d.ts +4 -0
  85. package/dist/types/graphs/Graph.d.ts +3 -1
  86. package/dist/types/graphs/acceptedModelResponse.d.ts +15 -0
  87. package/dist/types/messages/fading.d.ts +14 -2
  88. package/dist/types/messages/prune.d.ts +7 -0
  89. package/dist/types/openai/arguments.d.ts +2 -0
  90. package/dist/types/openai/index.d.ts +2 -0
  91. package/dist/types/openai/toolProjection.d.ts +30 -0
  92. package/dist/types/run.d.ts +1 -0
  93. package/dist/types/tools/ArtifactTruncation.d.ts +3 -0
  94. package/dist/types/tools/ToolNode.d.ts +1 -1
  95. package/dist/types/types/graph.d.ts +5 -3
  96. package/dist/types/types/run.d.ts +5 -0
  97. package/dist/types/types/stream.d.ts +22 -1
  98. package/dist/types/types/tools.d.ts +14 -0
  99. package/dist/types/utils/acceptedToolArguments.d.ts +10 -0
  100. package/package.json +1 -1
  101. package/src/common/enum.ts +5 -0
  102. package/src/events.ts +24 -1
  103. package/src/graphs/Graph.ts +95 -0
  104. package/src/graphs/acceptedModelResponse.ts +307 -0
  105. package/src/llm/invoke.ts +39 -14
  106. package/src/llm/streamLimits.ts +1 -1
  107. package/src/messages/fading.ts +30 -5
  108. package/src/messages/prune.ts +168 -55
  109. package/src/openai/arguments.ts +2 -0
  110. package/src/openai/index.ts +6 -0
  111. package/src/openai/toolProjection.ts +318 -0
  112. package/src/run.ts +21 -0
  113. package/src/session/AgentSession.ts +2 -2
  114. package/src/stream.ts +21 -1
  115. package/src/tools/ArtifactTruncation.ts +85 -0
  116. package/src/tools/BashExecutor.ts +22 -5
  117. package/src/tools/CodeExecutor.ts +18 -1
  118. package/src/tools/ProgrammaticToolCalling.ts +17 -3
  119. package/src/tools/ToolNode.ts +5 -0
  120. package/src/tools/subagent/SubagentReplay.ts +10 -9
  121. package/src/types/graph.ts +15 -9
  122. package/src/types/run.ts +5 -0
  123. package/src/types/stream.ts +26 -0
  124. package/src/types/tools.ts +21 -0
  125. package/src/utils/acceptedToolArguments.ts +204 -0
@@ -22,7 +22,6 @@ import {
22
22
  resolveCodeApiAuthHeaders,
23
23
  selectRuntimeSessionHint,
24
24
  } from './CodeExecutor';
25
- import { appendExecutionArtifactFileSummary } from './CodeSessionFileSummary';
26
25
  import {
27
26
  assertUnambiguousIdentifiers,
28
27
  projectProgrammaticToolMap,
@@ -39,10 +38,15 @@ import {
39
38
  appendArtifactDeliveryWarning,
40
39
  normalizeArtifactDeliveryFailure,
41
40
  } from '@/tools/ArtifactDelivery';
41
+ import {
42
+ appendArtifactTruncationWarning,
43
+ normalizeArtifactTruncation,
44
+ } from '@/tools/ArtifactTruncation';
42
45
  import {
43
46
  describeCodeApiError,
44
47
  logCodeApiDiagnostic,
45
48
  } from '@/tools/diagnostics';
49
+ import { appendExecutionArtifactFileSummary } from './CodeSessionFileSummary';
46
50
  import { resolveFetchProxyAgent } from '@/utils/proxy';
47
51
  import { INTENT_PROPERTY } from '@/tools/intentArg';
48
52
  import { Constants } from '@/common';
@@ -946,11 +950,18 @@ export function formatCompletedResponse(
946
950
  outputWithReminder,
947
951
  artifactDelivery
948
952
  );
953
+ const artifactTruncation = normalizeArtifactTruncation(
954
+ response.artifact_truncation
955
+ );
956
+ const outputWithWarnings = appendArtifactTruncationWarning(
957
+ outputWithDeliveryWarning,
958
+ artifactTruncation
959
+ );
949
960
 
950
961
  return [
951
962
  filePersistence === 'execution'
952
- ? appendExecutionArtifactFileSummary(outputWithDeliveryWarning, response.files)
953
- : appendCodeSessionFileSummary(outputWithDeliveryWarning, response.files),
963
+ ? appendExecutionArtifactFileSummary(outputWithWarnings, response.files)
964
+ : appendCodeSessionFileSummary(outputWithWarnings, response.files),
954
965
  {
955
966
  session_id: response.session_id,
956
967
  files: response.files,
@@ -960,6 +971,9 @@ export function formatCompletedResponse(
960
971
  ...(artifactDelivery != null
961
972
  ? { artifact_delivery: artifactDelivery }
962
973
  : {}),
974
+ ...(artifactTruncation != null
975
+ ? { artifact_truncation: artifactTruncation }
976
+ : {}),
963
977
  ...(response.runtime_session_id != null
964
978
  ? {
965
979
  runtime_session_id: response.runtime_session_id,
@@ -1021,6 +1021,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
1021
1021
  preparedSubagents,
1022
1022
  restoreRunStepResumeState,
1023
1023
  createRunStepResumeState,
1024
+ onToolCallsClaimed,
1024
1025
  }: t.ToolNodeConstructorParams) {
1025
1026
  super({
1026
1027
  name: name ?? TOOL_NODE_RUN_NAME,
@@ -1155,6 +1156,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
1155
1156
  }
1156
1157
  const state = input as T & Pick<t.BaseGraphState, 'runStepState'>;
1157
1158
  restoreRunStepResumeState?.(state.runStepState, config);
1159
+ // Freeze replay authority before observers can yield to sibling tasks.
1160
+ if (onToolCallsClaimed != null && assistantBatch?.message.id != null) {
1161
+ await onToolCallsClaimed(assistantBatch.message.id, config);
1162
+ }
1158
1163
  let result: T;
1159
1164
  try {
1160
1165
  result = await this.run(input, config, referenceReplay);
@@ -8,7 +8,7 @@ import type {
8
8
  RunStepResumeState,
9
9
  ToolSessionContext,
10
10
  } from '@/types';
11
- import { isFadingTier } from '@/messages/fading';
11
+ import { isFadingTier, isLegacyFadingTier } from '@/messages/fading';
12
12
  import {
13
13
  attachRunStepResumeState,
14
14
  getRunStepResumeState,
@@ -290,6 +290,11 @@ export function isToolOutputReferenceState(
290
290
  );
291
291
  }
292
292
 
293
+ /** A current tier, or a legacy one the run then drops so it re-derives. */
294
+ function isStoredFadingTier(value: unknown): boolean {
295
+ return isFadingTier(value) || isLegacyFadingTier(value);
296
+ }
297
+
293
298
  function isGraphResumeState(value: unknown): value is SubagentGraphResumeState {
294
299
  if (value == null || typeof value !== 'object') {
295
300
  return false;
@@ -306,15 +311,14 @@ function isGraphResumeState(value: unknown): value is SubagentGraphResumeState {
306
311
  !state.eagerToolUsage.every(isEagerToolUsageState) ||
307
312
  !Array.isArray(state.eagerToolSuppressions) ||
308
313
  !state.eagerToolSuppressions.every(isString) ||
309
- (state.runStepState != null &&
310
- !isRunStepResumeState(state.runStepState)) ||
314
+ (state.runStepState != null && !isRunStepResumeState(state.runStepState)) ||
311
315
  (state.toolOutputReferences != null &&
312
316
  !isToolOutputReferenceState(state.toolOutputReferences)) ||
313
- (state.fadingTier != null && !isFadingTier(state.fadingTier)) ||
317
+ (state.fadingTier != null && !isStoredFadingTier(state.fadingTier)) ||
314
318
  (state.fadingTiers != null &&
315
319
  (typeof state.fadingTiers !== 'object' ||
316
320
  Array.isArray(state.fadingTiers) ||
317
- !Object.values(state.fadingTiers).every(isFadingTier)))
321
+ !Object.values(state.fadingTiers).every(isStoredFadingTier)))
318
322
  ) {
319
323
  return false;
320
324
  }
@@ -515,10 +519,7 @@ export function attachSubagentResumeManifest(
515
519
  const runStepState = getRunStepResumeState(payload);
516
520
  if (runStepState != null) {
517
521
  return attachRunStepResumeState(
518
- attachSubagentResumeManifest(
519
- stripRunStepResumeState(payload),
520
- manifest
521
- ),
522
+ attachSubagentResumeManifest(stripRunStepResumeState(payload), manifest),
522
523
  runStepState
523
524
  );
524
525
  }
@@ -10,22 +10,24 @@ import type { START, StateGraph, StateGraphArgs } from '@langchain/langgraph';
10
10
  import type { RunnableConfig, Runnable } from '@langchain/core/runnables';
11
11
  import type { ChatGenerationChunk } from '@langchain/core/outputs';
12
12
  import type { GoogleAIToolType } from '@langchain/google-common';
13
- import type {
14
- SummarizationNodeInput,
15
- SummarizeCompleteEvent,
16
- CompactionSemanticIndex,
17
- SummarizationConfig,
18
- SummarizeStartEvent,
19
- SummarizeDeltaEvent,
20
- } from '@/types/summarize';
21
13
  import type {
22
14
  RunStep,
15
+ ModelResponseEvent,
16
+ ModelToolsClaimedEvent,
23
17
  RunStepDeltaEvent,
24
18
  RunStepResumeState,
25
19
  RunStepClosedEvent,
26
20
  MessageDeltaEvent,
27
21
  ReasoningDeltaEvent,
28
22
  } from '@/types/stream';
23
+ import type {
24
+ SummarizationNodeInput,
25
+ SummarizeCompleteEvent,
26
+ CompactionSemanticIndex,
27
+ SummarizationConfig,
28
+ SummarizeStartEvent,
29
+ SummarizeDeltaEvent,
30
+ } from '@/types/summarize';
29
31
  import type {
30
32
  ToolMap,
31
33
  ToolSessionMap,
@@ -183,7 +185,7 @@ export interface ContextUsageEvent {
183
185
  * `RunConfig.fadingTiers[agentId]`.
184
186
  */
185
187
  export interface FadingTier {
186
- v: 1;
188
+ v: 2;
187
189
  /** Token budget the caps derive from, in raw token space. Never grows;
188
190
  * clamped to the current context window when seeded. */
189
191
  budgetTokens: number;
@@ -202,6 +204,8 @@ export interface EventHandler {
202
204
  data:
203
205
  | StreamEventData
204
206
  | ModelEndData
207
+ | ModelResponseEvent
208
+ | ModelToolsClaimedEvent
205
209
  | RunStep
206
210
  | RunStepDeltaEvent
207
211
  | RunStepClosedEvent
@@ -406,6 +410,8 @@ export type StandardGraphInput = {
406
410
  agents: AgentInputs[];
407
411
  /** Execution backend used to resolve the effective tool registry. */
408
412
  toolExecution?: ToolExecutionConfig;
413
+ /** Trusted single-agent client delegation policy; mixed batches fail closed. */
414
+ clientDelegatedToolNames?: readonly string[];
409
415
  langfuse?: LangfuseConfig;
410
416
  tokenCounter?: TokenCounter;
411
417
  indexTokenCountMap?: Record<string, number>;
package/src/types/run.ts CHANGED
@@ -279,6 +279,11 @@ export type RunConfig = {
279
279
  */
280
280
  langfuse?: g.LangfuseConfig;
281
281
  customHandlers?: Record<string, g.EventHandler>;
282
+ /** Explicit client-owned tools for a single-agent graph. A batch mixing
283
+ * client and SDK/provider calls fails closed; omitted means SDK ownership.
284
+ * Hosts must register the corresponding model-facing tool schemas.
285
+ */
286
+ clientDelegatedToolNames?: readonly string[];
282
287
  /**
283
288
  * Receives token usage for every model call made inside subagent child
284
289
  * runs (including nested subagents). Child graphs execute via `invoke()`
@@ -1,5 +1,6 @@
1
1
  // src/types/stream.ts
2
2
  import type {
3
+ AIMessageChunk,
3
4
  MessageContentImageUrl,
4
5
  MessageContentText,
5
6
  ToolMessage,
@@ -15,6 +16,24 @@ import type { SummarizeCompleteEvent } from '@/types/summarize';
15
16
  import type { ToolEndEvent } from '@/types/tools';
16
17
  import { StepTypes, ContentTypes, GraphEvents } from '@/common/enum';
17
18
 
19
+ /** One accepted model result, detached from execution state before host dispatch.
20
+ * Provider chunks, failed attempts and UI run-step events are not this contract. */
21
+ export interface ModelResponseEvent {
22
+ type: 'model_response';
23
+ /** Graph-generated acceptance ID, not a provider ID or run-step index. */
24
+ id: string;
25
+ agentId: string;
26
+ /** Graph-state message identity used to correlate ToolNode ownership. */
27
+ messageId?: string;
28
+ toolCalls: ReadonlyArray<ToolCall>;
29
+ /** Same index as toolCalls. Only a trusted graph decision of 'client'
30
+ * permits this call on the OpenAI client wire; absence fails closed. */
31
+ toolCallDispositions: ReadonlyArray<'sdk' | 'provider' | 'client'>;
32
+ invalidToolCalls: ReadonlyArray<
33
+ NonNullable<AIMessageChunk['invalid_tool_calls']>[number]
34
+ >;
35
+ }
36
+
18
37
  export type HandleLLMEnd = (
19
38
  output: LLMResult,
20
39
  runId: string,
@@ -542,3 +561,10 @@ export type ContentAggregatorResult = {
542
561
  contentParts: Array<MessageContentComplex | undefined>;
543
562
  aggregateContent: ContentAggregator;
544
563
  };
564
+
565
+ /** Ownership, not successful completion. Interrupted/failed batches remain graph-owned. */
566
+ export interface ModelToolsClaimedEvent {
567
+ type: 'model_tools_claimed';
568
+ agentId: string;
569
+ messageId: string;
570
+ }
@@ -313,6 +313,8 @@ export type ToolNodeOptions = {
313
313
  ) => void;
314
314
  /** SDK-owned checkpoint snapshot for open run-step lifecycle state. */
315
315
  createRunStepResumeState?: () => RunStepResumeState;
316
+ /** Internal ownership bridge, awaited before ToolNode can execute a batch. */
317
+ onToolCallsClaimed?: (messageId: string, config: RunnableConfig) => Promise<void>;
316
318
  };
317
319
 
318
320
  export type ToolNodeConstructorParams = ToolRefs & ToolNodeOptions;
@@ -495,6 +497,21 @@ export type ArtifactDeliveryFailure = {
495
497
  failed: number;
496
498
  };
497
499
 
500
+ export type ArtifactTruncationReason =
501
+ | 'max_files'
502
+ | 'depth'
503
+ | 'size'
504
+ | 'path'
505
+ | 'unreadable';
506
+
507
+ export type ArtifactTruncation = {
508
+ code: 'artifact_truncated';
509
+ reasons: Partial<Record<ArtifactTruncationReason, number>>;
510
+ /** Up to 20 relative paths omitted from the response. */
511
+ skipped: string[];
512
+ skipped_count: number;
513
+ };
514
+
498
515
  export type ExecuteResult = {
499
516
  /**
500
517
  * Execution session id — the (transient) sandbox run that produced
@@ -508,6 +525,7 @@ export type ExecuteResult = {
508
525
  /** Persisted input paths explicitly removed during this execution. */
509
526
  deleted_files?: string[];
510
527
  artifact_delivery?: ArtifactDeliveryFailure;
528
+ artifact_truncation?: ArtifactTruncation;
511
529
  /**
512
530
  * Durable runtime session id echoed by a stateful Code API backend
513
531
  * (hash of tenant+user+hint). Additive; absent on stateless servers.
@@ -1354,6 +1372,7 @@ export type ProgrammaticExecutionResponse = {
1354
1372
  /** Persisted input paths explicitly removed during this execution. */
1355
1373
  deleted_files?: string[];
1356
1374
  artifact_delivery?: ArtifactDeliveryFailure;
1375
+ artifact_truncation?: ArtifactTruncation;
1357
1376
 
1358
1377
  /** Durable runtime session echo from a stateful backend (additive). */
1359
1378
  runtime_session_id?: string;
@@ -1373,6 +1392,7 @@ export type ProgrammaticExecutionArtifact = {
1373
1392
  /** Persisted input paths explicitly removed during this execution. */
1374
1393
  deleted_files?: string[];
1375
1394
  artifact_delivery?: ArtifactDeliveryFailure;
1395
+ artifact_truncation?: ArtifactTruncation;
1376
1396
  /** Durable runtime session echo from a stateful backend (additive). */
1377
1397
  runtime_session_id?: string;
1378
1398
  runtime_status?: 'new' | 'reused';
@@ -1455,6 +1475,7 @@ export type CodeExecutionArtifact = {
1455
1475
  /** Persisted input paths explicitly removed during this execution. */
1456
1476
  deleted_files?: string[];
1457
1477
  artifact_delivery?: ArtifactDeliveryFailure;
1478
+ artifact_truncation?: ArtifactTruncation;
1458
1479
  /** Durable runtime session echo from a stateful backend (additive). */
1459
1480
  runtime_session_id?: string;
1460
1481
  runtime_status?: 'new' | 'reused';
@@ -0,0 +1,204 @@
1
+ import { types } from 'node:util';
2
+
3
+ const MAX_ARGUMENT_DEPTH = 64;
4
+ const INVALID = 'Accepted tool call arguments are not JSON serializable';
5
+ const LIMIT = 'Tool projection buffer limit exceeded';
6
+
7
+ function invalid(): never {
8
+ throw new Error(INVALID);
9
+ }
10
+
11
+ /** structuredClone and VM providers may return objects from another realm. */
12
+ function hasJSONPrototype(value: object, array: boolean): boolean {
13
+ const proto: unknown = Object.getPrototypeOf(value);
14
+ if (proto === null) return true;
15
+ if (typeof proto !== 'object' || types.isProxy(proto)) return false;
16
+ const ctor: unknown = Object.getOwnPropertyDescriptor(
17
+ proto,
18
+ 'constructor'
19
+ )?.value;
20
+ if (typeof ctor !== 'function' || types.isProxy(ctor)) return false;
21
+ return (
22
+ Object.getOwnPropertyDescriptor(ctor, 'prototype')?.value === proto &&
23
+ Function.prototype.toString.call(ctor) ===
24
+ Function.prototype.toString.call(array ? Array : Object)
25
+ );
26
+ }
27
+
28
+ /** Encode JSON data, not arbitrary JS values. No getters, proxies or toJSON hooks run.
29
+ * Repeated references expand like JSON (and count against the budget); cycles fail.
30
+ * Limits bound output and traversal, including deeply nested/alias-heavy input.
31
+ */
32
+ export function serializeToolArguments(
33
+ value: unknown,
34
+ maxBytes: number
35
+ ): string {
36
+ if (maxBytes < 2) throw new Error(LIMIT);
37
+ const parts: string[] = [];
38
+ const ancestors = new Set<object>();
39
+ let remaining = maxBytes;
40
+ const append = (text: string): void => {
41
+ remaining -= Buffer.byteLength(text, 'utf8');
42
+ if (remaining < 0) throw new Error(LIMIT);
43
+ parts.push(text);
44
+ };
45
+ const string = (text: string): void => {
46
+ // Every UTF-16 unit needs at least one encoded byte. Check before allocating escapes.
47
+ if (text.length + 2 > remaining) throw new Error(LIMIT);
48
+ append(JSON.stringify(text));
49
+ };
50
+ const encode = (input: unknown, depth: number): void => {
51
+ if (depth > MAX_ARGUMENT_DEPTH) invalid();
52
+ if (input === null) {
53
+ append('null');
54
+ return;
55
+ }
56
+ if (typeof input === 'string') {
57
+ string(input);
58
+ return;
59
+ }
60
+ if (typeof input === 'boolean') {
61
+ append(input ? 'true' : 'false');
62
+ return;
63
+ }
64
+ if (typeof input === 'number') {
65
+ if (!Number.isFinite(input)) invalid();
66
+ append(Object.is(input, -0) ? '-0' : String(input));
67
+ return;
68
+ }
69
+ if (typeof input !== 'object') invalid();
70
+ if (types.isProxy(input) || ancestors.has(input)) invalid();
71
+ const array = Array.isArray(input);
72
+ if (!hasJSONPrototype(input, array)) invalid();
73
+ const keys = Reflect.ownKeys(input);
74
+ // At least one byte per entry, plus punctuation. Bound descriptor work up front.
75
+ if (keys.length > remaining + (array ? 1 : 0)) throw new Error(LIMIT);
76
+ ancestors.add(input);
77
+ if (array) {
78
+ // Dense arrays only, with no symbols, hidden entries or extra named properties.
79
+ if (keys.length !== input.length + 1) invalid();
80
+ append('[');
81
+ for (let i = 0; i < input.length; i++) {
82
+ const descriptor = Object.getOwnPropertyDescriptor(input, String(i));
83
+ if (
84
+ descriptor == null ||
85
+ descriptor.enumerable !== true ||
86
+ !('value' in descriptor)
87
+ )
88
+ invalid();
89
+ if (i !== 0) append(',');
90
+ encode(descriptor.value, depth + 1);
91
+ }
92
+ append(']');
93
+ } else {
94
+ append('{');
95
+ for (let i = 0; i < keys.length; i++) {
96
+ const key = keys[i];
97
+ if (typeof key !== 'string') invalid();
98
+ const descriptor = Object.getOwnPropertyDescriptor(input, key);
99
+ if (
100
+ descriptor == null ||
101
+ descriptor.enumerable !== true ||
102
+ !('value' in descriptor)
103
+ )
104
+ invalid();
105
+ if (i !== 0) append(',');
106
+ string(key);
107
+ append(':');
108
+ encode(descriptor.value, depth + 1);
109
+ }
110
+ append('}');
111
+ }
112
+ ancestors.delete(input);
113
+ };
114
+ if (value != null && typeof value === 'object' && types.isProxy(value))
115
+ invalid();
116
+ if (value == null || typeof value !== 'object' || Array.isArray(value)) {
117
+ throw new Error('Accepted tool call arguments must be an object');
118
+ }
119
+ encode(value, 0);
120
+ return parts.join('');
121
+ }
122
+
123
+ /** Detach JSON data without encoding it. Unlike projection, invocation has no
124
+ * formatting budget: preserve shared references and traverse iteratively so a
125
+ * deep or alias-heavy object cannot expand exponentially during validation.
126
+ */
127
+ export function cloneToolArguments(value: unknown): Record<string, unknown> {
128
+ const copies = new Map<object, object>();
129
+ const ancestors = new Set<object>();
130
+ const stack: Array<{
131
+ input: object;
132
+ output: object;
133
+ keys: (string | symbol)[];
134
+ index: number;
135
+ }> = [];
136
+ const copy = (input: unknown): unknown => {
137
+ if (
138
+ input === null ||
139
+ typeof input === 'string' ||
140
+ typeof input === 'boolean'
141
+ )
142
+ return input;
143
+ if (typeof input === 'number') {
144
+ if (!Number.isFinite(input)) invalid();
145
+ return input;
146
+ }
147
+ if (
148
+ typeof input !== 'object' ||
149
+ types.isProxy(input) ||
150
+ ancestors.has(input)
151
+ )
152
+ invalid();
153
+ const existing = copies.get(input);
154
+ if (existing != null) return existing;
155
+ const array = Array.isArray(input);
156
+ if (!hasJSONPrototype(input, array)) invalid();
157
+ const keys = Reflect.ownKeys(input);
158
+ if (array && keys.length !== input.length + 1) invalid();
159
+ const output: object = array ? [] : {};
160
+ copies.set(input, output);
161
+ ancestors.add(input);
162
+ stack.push({
163
+ input,
164
+ output,
165
+ keys: array ? keys.filter((key) => key !== 'length') : keys,
166
+ index: 0,
167
+ });
168
+ return output;
169
+ };
170
+ if (
171
+ value == null ||
172
+ typeof value !== 'object' ||
173
+ types.isProxy(value) ||
174
+ Array.isArray(value)
175
+ )
176
+ invalid();
177
+ const result = copy(value) as Record<string, unknown>;
178
+ while (stack.length > 0) {
179
+ const frame = stack[stack.length - 1];
180
+ if (frame.index === frame.keys.length) {
181
+ ancestors.delete(frame.input);
182
+ stack.pop();
183
+ continue;
184
+ }
185
+ const key = frame.keys[frame.index++];
186
+ if (typeof key !== 'string') invalid();
187
+ if (Array.isArray(frame.input) && key !== String(frame.index - 1))
188
+ invalid();
189
+ const descriptor = Object.getOwnPropertyDescriptor(frame.input, key);
190
+ if (
191
+ descriptor == null ||
192
+ !('value' in descriptor) ||
193
+ descriptor.enumerable !== true
194
+ )
195
+ invalid();
196
+ Object.defineProperty(frame.output, key, {
197
+ value: copy(descriptor.value),
198
+ enumerable: true,
199
+ writable: true,
200
+ configurable: true,
201
+ });
202
+ }
203
+ return result;
204
+ }