@librechat/agents 4.0.2 → 4.0.3

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 (156) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +3 -2
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/decisions/structuredChat.cjs +2 -8
  4. package/dist/cjs/decisions/structuredChat.cjs.map +1 -1
  5. package/dist/cjs/decisions/structuredOutput.cjs +18 -0
  6. package/dist/cjs/decisions/structuredOutput.cjs.map +1 -0
  7. package/dist/cjs/graphs/Graph.cjs +25 -9
  8. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  9. package/dist/cjs/llm/google/index.cjs +1 -1
  10. package/dist/cjs/llm/invoke.cjs +34 -12
  11. package/dist/cjs/llm/invoke.cjs.map +1 -1
  12. package/dist/cjs/llm/openai/index.cjs +7 -0
  13. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  14. package/dist/cjs/llm/providerTextBoundary.cjs +355 -0
  15. package/dist/cjs/llm/providerTextBoundary.cjs.map +1 -0
  16. package/dist/cjs/llm/providerTextChunk.cjs +14 -0
  17. package/dist/cjs/llm/providerTextChunk.cjs.map +1 -0
  18. package/dist/cjs/llm/providerTextControls.cjs +236 -0
  19. package/dist/cjs/llm/providerTextControls.cjs.map +1 -0
  20. package/dist/cjs/llm/stream/smoother.cjs +2 -1
  21. package/dist/cjs/llm/stream/smoother.cjs.map +1 -1
  22. package/dist/cjs/main.cjs +10 -1
  23. package/dist/cjs/protection/providerText.cjs +224 -0
  24. package/dist/cjs/protection/providerText.cjs.map +1 -0
  25. package/dist/cjs/protection/providerTextInput.cjs +25 -0
  26. package/dist/cjs/protection/providerTextInput.cjs.map +1 -0
  27. package/dist/cjs/run.cjs +7 -1
  28. package/dist/cjs/run.cjs.map +1 -1
  29. package/dist/cjs/stream.cjs +42 -14
  30. package/dist/cjs/stream.cjs.map +1 -1
  31. package/dist/cjs/summarization/node.cjs +26 -8
  32. package/dist/cjs/summarization/node.cjs.map +1 -1
  33. package/dist/cjs/tools/SubagentTool.cjs +11 -3
  34. package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
  35. package/dist/cjs/tools/ToolNode.cjs +6 -5
  36. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  37. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +2 -2
  38. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -1
  39. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +66 -16
  40. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  41. package/dist/cjs/tools/subagent/SubagentReplay.cjs +4 -2
  42. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
  43. package/dist/cjs/tools/subagent/childGraphConfig.cjs +3 -1
  44. package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -1
  45. package/dist/cjs/tools/subagent/diagnostics.cjs +48 -5
  46. package/dist/cjs/tools/subagent/diagnostics.cjs.map +1 -1
  47. package/dist/cjs/tools/subagent/hostArgs.cjs +214 -0
  48. package/dist/cjs/tools/subagent/hostArgs.cjs.map +1 -0
  49. package/dist/cjs/tools/subagent/index.cjs +1 -0
  50. package/dist/cjs/tools/toolErrorContent.cjs +1 -1
  51. package/dist/esm/agents/AgentContext.mjs +3 -2
  52. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  53. package/dist/esm/decisions/structuredChat.mjs +2 -8
  54. package/dist/esm/decisions/structuredChat.mjs.map +1 -1
  55. package/dist/esm/decisions/structuredOutput.mjs +18 -0
  56. package/dist/esm/decisions/structuredOutput.mjs.map +1 -0
  57. package/dist/esm/graphs/Graph.mjs +25 -9
  58. package/dist/esm/graphs/Graph.mjs.map +1 -1
  59. package/dist/esm/llm/google/index.mjs +1 -1
  60. package/dist/esm/llm/invoke.mjs +34 -12
  61. package/dist/esm/llm/invoke.mjs.map +1 -1
  62. package/dist/esm/llm/openai/index.mjs +7 -0
  63. package/dist/esm/llm/openai/index.mjs.map +1 -1
  64. package/dist/esm/llm/providerTextBoundary.mjs +355 -0
  65. package/dist/esm/llm/providerTextBoundary.mjs.map +1 -0
  66. package/dist/esm/llm/providerTextChunk.mjs +13 -0
  67. package/dist/esm/llm/providerTextChunk.mjs.map +1 -0
  68. package/dist/esm/llm/providerTextControls.mjs +230 -0
  69. package/dist/esm/llm/providerTextControls.mjs.map +1 -0
  70. package/dist/esm/llm/stream/smoother.mjs +2 -1
  71. package/dist/esm/llm/stream/smoother.mjs.map +1 -1
  72. package/dist/esm/main.mjs +5 -3
  73. package/dist/esm/protection/providerText.mjs +218 -0
  74. package/dist/esm/protection/providerText.mjs.map +1 -0
  75. package/dist/esm/protection/providerTextInput.mjs +24 -0
  76. package/dist/esm/protection/providerTextInput.mjs.map +1 -0
  77. package/dist/esm/run.mjs +7 -1
  78. package/dist/esm/run.mjs.map +1 -1
  79. package/dist/esm/stream.mjs +42 -14
  80. package/dist/esm/stream.mjs.map +1 -1
  81. package/dist/esm/summarization/node.mjs +26 -8
  82. package/dist/esm/summarization/node.mjs.map +1 -1
  83. package/dist/esm/tools/SubagentTool.mjs +11 -3
  84. package/dist/esm/tools/SubagentTool.mjs.map +1 -1
  85. package/dist/esm/tools/ToolNode.mjs +6 -5
  86. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  87. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +2 -2
  88. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -1
  89. package/dist/esm/tools/subagent/SubagentExecutor.mjs +66 -16
  90. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  91. package/dist/esm/tools/subagent/SubagentReplay.mjs +4 -2
  92. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
  93. package/dist/esm/tools/subagent/childGraphConfig.mjs +3 -1
  94. package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -1
  95. package/dist/esm/tools/subagent/diagnostics.mjs +46 -6
  96. package/dist/esm/tools/subagent/diagnostics.mjs.map +1 -1
  97. package/dist/esm/tools/subagent/hostArgs.mjs +207 -0
  98. package/dist/esm/tools/subagent/hostArgs.mjs.map +1 -0
  99. package/dist/esm/tools/subagent/index.mjs +1 -0
  100. package/dist/esm/tools/toolErrorContent.mjs +1 -1
  101. package/dist/types/decisions/structuredChat.d.ts +3 -2
  102. package/dist/types/decisions/structuredOutput.d.ts +4 -0
  103. package/dist/types/graphs/Graph.d.ts +6 -3
  104. package/dist/types/index.d.ts +2 -0
  105. package/dist/types/llm/invoke.d.ts +4 -1
  106. package/dist/types/llm/providerTextBoundary.d.ts +4 -0
  107. package/dist/types/llm/providerTextChunk.d.ts +7 -0
  108. package/dist/types/llm/providerTextControls.d.ts +18 -0
  109. package/dist/types/protection/providerText.d.ts +77 -0
  110. package/dist/types/protection/providerTextInput.d.ts +3 -0
  111. package/dist/types/run.d.ts +1 -0
  112. package/dist/types/tools/BashExecutor.d.ts +4 -0
  113. package/dist/types/tools/CodeExecutor.d.ts +4 -0
  114. package/dist/types/tools/ReadFile.d.ts +4 -0
  115. package/dist/types/tools/SkillTool.d.ts +4 -0
  116. package/dist/types/tools/SubagentTool.d.ts +2 -0
  117. package/dist/types/tools/ToolSearch.d.ts +4 -0
  118. package/dist/types/tools/search/schema.d.ts +4 -0
  119. package/dist/types/tools/subagent/SubagentExecutionRegistry.d.ts +2 -0
  120. package/dist/types/tools/subagent/SubagentExecutor.d.ts +10 -2
  121. package/dist/types/tools/subagent/SubagentReplay.d.ts +3 -1
  122. package/dist/types/tools/subagent/diagnostics.d.ts +27 -3
  123. package/dist/types/tools/subagent/hostArgs.d.ts +57 -0
  124. package/dist/types/tools/subagent/index.d.ts +3 -2
  125. package/dist/types/types/graph.d.ts +36 -0
  126. package/dist/types/types/run.d.ts +4 -2
  127. package/dist/types/types/tools.d.ts +2 -0
  128. package/package.json +4 -4
  129. package/src/agents/AgentContext.ts +3 -2
  130. package/src/decisions/structuredChat.ts +9 -15
  131. package/src/decisions/structuredOutput.ts +25 -0
  132. package/src/graphs/Graph.ts +53 -9
  133. package/src/index.ts +7 -0
  134. package/src/llm/invoke.ts +48 -7
  135. package/src/llm/openai/index.ts +8 -0
  136. package/src/llm/providerTextBoundary.ts +446 -0
  137. package/src/llm/providerTextChunk.ts +15 -0
  138. package/src/llm/providerTextControls.ts +173 -0
  139. package/src/llm/stream/smoother.ts +4 -2
  140. package/src/protection/providerText.ts +325 -0
  141. package/src/protection/providerTextInput.ts +21 -0
  142. package/src/run.ts +9 -0
  143. package/src/stream.ts +33 -7
  144. package/src/summarization/node.ts +32 -14
  145. package/src/tools/SubagentTool.ts +14 -3
  146. package/src/tools/ToolNode.ts +7 -3
  147. package/src/tools/subagent/SubagentExecutionRegistry.ts +6 -2
  148. package/src/tools/subagent/SubagentExecutor.ts +145 -18
  149. package/src/tools/subagent/SubagentReplay.ts +10 -3
  150. package/src/tools/subagent/childGraphConfig.ts +12 -1
  151. package/src/tools/subagent/diagnostics.ts +98 -6
  152. package/src/tools/subagent/hostArgs.ts +413 -0
  153. package/src/tools/subagent/index.ts +9 -0
  154. package/src/types/graph.ts +41 -0
  155. package/src/types/run.ts +4 -2
  156. package/src/types/tools.ts +6 -1
@@ -1,3 +1,4 @@
1
+ import type { SubagentHostArgs } from '@/types/graph';
1
2
  import {
2
3
  SubagentSettlementBindingError,
3
4
  SubagentDefinitionBindingError,
@@ -5,11 +6,39 @@ import {
5
6
  SubagentExecutionInvalidatedError,
6
7
  } from './SubagentExecutionRegistry';
7
8
  import { describeCodeApiError } from '@/tools/diagnostics';
9
+ import { isSubagentHostArgName } from './hostArgs';
8
10
 
9
11
  /** Which step of child start-up failed: execution identity, then host config resolution. */
10
12
  export type SubagentResolutionPhase = 'identity' | 'config';
11
13
 
14
+ /** Why a host resolver refused a declared host argument value. */
15
+ export type SubagentHostArgumentRejection = 'unavailable' | 'not_allowed';
16
+
17
+ /**
18
+ * Thrown by a lazy resolver to refuse one declared host argument value. The
19
+ * parent model receives a fixed message naming the argument; the value and
20
+ * any error text stay private.
21
+ */
22
+ export class SubagentHostArgumentError extends Error {
23
+ readonly argument: string;
24
+ readonly rejection: SubagentHostArgumentRejection;
25
+
26
+ constructor(argument: string, rejection: SubagentHostArgumentRejection) {
27
+ super('Subagent host argument was rejected.');
28
+ this.name = 'SubagentHostArgumentError';
29
+ this.argument = argument;
30
+ this.rejection = rejection;
31
+ }
32
+ }
33
+
34
+ /** Safe host-argument refusal retained across the detached delivery boundary. */
35
+ export type SubagentHostArgumentFailure = {
36
+ argument: string;
37
+ rejection: SubagentHostArgumentRejection;
38
+ };
39
+
12
40
  const SUBAGENT_ERROR_TYPES = [
41
+ ['SubagentHostArgumentError', SubagentHostArgumentError],
13
42
  ['SubagentExecutionInvalidatedError', SubagentExecutionInvalidatedError],
14
43
  ['SubagentDefinitionBindingError', SubagentDefinitionBindingError],
15
44
  ['SubagentInvocationBindingError', SubagentInvocationBindingError],
@@ -27,6 +56,8 @@ const SUBAGENT_RESOLUTION_MESSAGES = {
27
56
  model_unavailable: 'Subagent error: Model or provider unavailable.',
28
57
  configuration_changed:
29
58
  'Subagent error: Subagent configuration changed. Start a new execution.',
59
+ host_argument_rejected:
60
+ 'Subagent error: A requested subagent argument is not available. Omit it to let the host choose, or pass a different value.',
30
61
  unknown: 'Subagent error: Unable to initialize the selected subagent.',
31
62
  } as const;
32
63
 
@@ -67,6 +98,7 @@ function normalizeResolutionCause(
67
98
  case 'agent_unavailable':
68
99
  case 'model_unavailable':
69
100
  case 'configuration_changed':
101
+ case 'host_argument_rejected':
70
102
  return cause;
71
103
  default:
72
104
  return 'unknown';
@@ -79,16 +111,68 @@ export function getSubagentResolutionFailureMessage(
79
111
  return SUBAGENT_RESOLUTION_MESSAGES[normalizeResolutionCause(cause)];
80
112
  }
81
113
 
114
+ /** Returns a resolver's typed host-argument refusal when its name is safe to show. */
115
+ export function getSubagentHostArgumentFailure(
116
+ error: unknown,
117
+ suppliedHostArgs: SubagentHostArgs | undefined
118
+ ): SubagentHostArgumentFailure | undefined {
119
+ try {
120
+ if (
121
+ !(error instanceof SubagentHostArgumentError) ||
122
+ suppliedHostArgs == null
123
+ ) {
124
+ return undefined;
125
+ }
126
+ const { argument, rejection } = error;
127
+ if (
128
+ typeof argument !== 'string' ||
129
+ !isSubagentHostArgName(argument) ||
130
+ !Object.prototype.hasOwnProperty.call(suppliedHostArgs, argument)
131
+ ) {
132
+ return undefined;
133
+ }
134
+ return {
135
+ argument,
136
+ rejection: rejection === 'unavailable' ? 'unavailable' : 'not_allowed',
137
+ };
138
+ } catch {
139
+ return undefined;
140
+ }
141
+ }
142
+
143
+ /** Model-facing message for a refused host argument. */
144
+ export function getSubagentHostArgumentFailureMessage(
145
+ failure: SubagentHostArgumentFailure
146
+ ): string {
147
+ const { argument } = failure;
148
+ const omit = `Omit "${argument}" to let the host choose, or pass a different value.`;
149
+ return failure.rejection === 'unavailable'
150
+ ? `Subagent error: The requested "${argument}" is unavailable right now. ${omit}`
151
+ : `Subagent error: The requested "${argument}" is not allowed for this subagent. ${omit}`;
152
+ }
153
+
82
154
  /** Safe typed failure retained when detached execution crosses the host boundary. */
83
155
  export class SubagentResolutionError extends Error {
84
156
  readonly phase: SubagentResolutionPhase;
85
157
  readonly resolutionCause: SubagentResolutionCause;
158
+ readonly hostArgument?: SubagentHostArgumentFailure;
86
159
 
87
- constructor(phase: SubagentResolutionPhase, cause: SubagentResolutionCause) {
88
- super(getSubagentResolutionFailureMessage(cause));
160
+ constructor(
161
+ phase: SubagentResolutionPhase,
162
+ cause: SubagentResolutionCause,
163
+ hostArgument?: SubagentHostArgumentFailure
164
+ ) {
165
+ super(
166
+ hostArgument == null
167
+ ? getSubagentResolutionFailureMessage(cause)
168
+ : getSubagentHostArgumentFailureMessage(hostArgument)
169
+ );
89
170
  this.name = 'SubagentResolutionError';
90
171
  this.phase = phase;
91
172
  this.resolutionCause = normalizeResolutionCause(cause);
173
+ if (hostArgument != null) {
174
+ this.hostArgument = hostArgument;
175
+ }
92
176
  }
93
177
  }
94
178
 
@@ -111,7 +195,8 @@ export function logSubagentResolutionFailure(
111
195
  signal: AbortSignal,
112
196
  error: unknown,
113
197
  context?: SubagentResolutionContext,
114
- onResolutionFailure?: SubagentResolutionFailureHandler
198
+ onResolutionFailure?: SubagentResolutionFailureHandler,
199
+ suppliedHostArgs?: SubagentHostArgs
115
200
  ): SubagentResolutionDiagnostic {
116
201
  const detail: SubagentResolutionDiagnostic = {
117
202
  ...context,
@@ -122,11 +207,18 @@ export function logSubagentResolutionFailure(
122
207
  cause: 'unknown',
123
208
  message: SUBAGENT_RESOLUTION_MESSAGES.unknown,
124
209
  };
210
+ const hostArgumentRejected =
211
+ getSubagentHostArgumentFailure(error, suppliedHostArgs) != null;
212
+ if (hostArgumentRejected) {
213
+ detail.cause = 'host_argument_rejected';
214
+ detail.message = SUBAGENT_RESOLUTION_MESSAGES.host_argument_rejected;
215
+ }
125
216
  if (onResolutionFailure != null) {
126
217
  try {
127
- const cause = normalizeResolutionCause(
128
- onResolutionFailure(Object.freeze(detail), error)
129
- );
218
+ const reported = onResolutionFailure(Object.freeze({ ...detail }), error);
219
+ const cause = hostArgumentRejected
220
+ ? detail.cause
221
+ : normalizeResolutionCause(reported);
130
222
  return {
131
223
  ...detail,
132
224
  cause,
@@ -0,0 +1,413 @@
1
+ import { createHash } from 'crypto';
2
+ import type {
3
+ SubagentHostArgs,
4
+ SubagentHostArgSpec,
5
+ SubagentHostArgSpecs,
6
+ } from '@/types/graph';
7
+ import type { JsonSchemaType } from '@/types/tools';
8
+
9
+ /** Bounds on host-declared subagent call arguments and their values. */
10
+ export const SUBAGENT_HOST_ARG_LIMITS = Object.freeze({
11
+ argsPerSubagent: 8,
12
+ distinctArgs: 16,
13
+ enumValues: 64,
14
+ mergedEnumValues: 256,
15
+ valueLength: 256,
16
+ descriptionLength: 1024,
17
+ });
18
+
19
+ const HOST_ARG_NAME_PATTERN = /^[a-z][a-z0-9_]{0,63}$/;
20
+ /** Advertises the runtime's no-control-character rule; fixed and linear-time. */
21
+ const FREE_FORM_SCHEMA_PATTERN = '^[^\\u0000-\\u001f\\u007f]*$';
22
+ const RESERVED_HOST_ARG_NAMES: ReadonlySet<string> = new Set([
23
+ 'intent',
24
+ 'description',
25
+ 'subagent_type',
26
+ 'run_in_background',
27
+ 'subagent_thread_id',
28
+ ]);
29
+
30
+ /** Any subagent entry that may declare host arguments. */
31
+ export type SubagentHostArgDeclaration = {
32
+ type: string;
33
+ hostArgs?: SubagentHostArgSpecs;
34
+ };
35
+
36
+ export type SubagentHostArgsResult =
37
+ | { ok: true; hostArgs?: SubagentHostArgs }
38
+ | { ok: false; message: string };
39
+
40
+ type HostArgEntry = readonly [string, SubagentHostArgSpec];
41
+
42
+ type MergedHostArg = {
43
+ description: string;
44
+ values: string[];
45
+ seenValues: Set<string>;
46
+ freeForm: boolean;
47
+ maxLength: number;
48
+ };
49
+
50
+ /** A name a host may declare: lowercase snake case, not a built-in subagent argument. */
51
+ export function isSubagentHostArgName(name: string): boolean {
52
+ return HOST_ARG_NAME_PATTERN.test(name) && !RESERVED_HOST_ARG_NAMES.has(name);
53
+ }
54
+
55
+ /** Length in Unicode code points, the unit JSON Schema `maxLength` counts. */
56
+ function countCodePoints(value: string): number {
57
+ let count = 0;
58
+ for (const _ of value) {
59
+ count += 1;
60
+ }
61
+ return count;
62
+ }
63
+
64
+ function hasControlCharacter(value: string): boolean {
65
+ for (let i = 0; i < value.length; i++) {
66
+ const code = value.charCodeAt(i);
67
+ if (code < 0x20 || code === 0x7f) {
68
+ return true;
69
+ }
70
+ }
71
+ return false;
72
+ }
73
+
74
+ function isBoundedText(value: unknown, maxLength: number): value is string {
75
+ return (
76
+ typeof value === 'string' &&
77
+ value.trim().length > 0 &&
78
+ countCodePoints(value) <= maxLength &&
79
+ !hasControlCharacter(value)
80
+ );
81
+ }
82
+
83
+ function getFreeFormMaxLength(spec: SubagentHostArgSpec): number {
84
+ return spec.maxLength ?? SUBAGENT_HOST_ARG_LIMITS.valueLength;
85
+ }
86
+
87
+ function validateEnum(label: string, values: readonly string[]): void {
88
+ if (
89
+ !Array.isArray(values) ||
90
+ values.length === 0 ||
91
+ values.length > SUBAGENT_HOST_ARG_LIMITS.enumValues
92
+ ) {
93
+ throw new Error(
94
+ `${label} enum must list 1-${SUBAGENT_HOST_ARG_LIMITS.enumValues} values.`
95
+ );
96
+ }
97
+ const seen = new Set<string>();
98
+ for (const value of values) {
99
+ if (!isBoundedText(value, SUBAGENT_HOST_ARG_LIMITS.valueLength)) {
100
+ throw new Error(
101
+ `${label} enum values must be non-blank strings of at most ${SUBAGENT_HOST_ARG_LIMITS.valueLength} characters without control characters.`
102
+ );
103
+ }
104
+ if (seen.has(value)) {
105
+ throw new Error(`${label} enum values must be unique.`);
106
+ }
107
+ seen.add(value);
108
+ }
109
+ }
110
+
111
+ function validateFreeForm(label: string, spec: SubagentHostArgSpec): void {
112
+ const maxLength = getFreeFormMaxLength(spec);
113
+ if (
114
+ !Number.isSafeInteger(maxLength) ||
115
+ maxLength < 1 ||
116
+ maxLength > SUBAGENT_HOST_ARG_LIMITS.valueLength
117
+ ) {
118
+ throw new Error(
119
+ `${label} maxLength must be an integer from 1 to ${SUBAGENT_HOST_ARG_LIMITS.valueLength}.`
120
+ );
121
+ }
122
+ }
123
+
124
+ function validateSpec(
125
+ type: string,
126
+ name: string,
127
+ spec: SubagentHostArgSpec | undefined
128
+ ): asserts spec is SubagentHostArgSpec {
129
+ const label = `Subagent "${type}" host argument "${name}"`;
130
+ if (!isSubagentHostArgName(name)) {
131
+ throw new Error(
132
+ `${label} must match ${HOST_ARG_NAME_PATTERN.source} and must not reuse a built-in subagent argument name.`
133
+ );
134
+ }
135
+ if (spec == null) {
136
+ throw new Error(`${label} must be an object.`);
137
+ }
138
+ if ((spec as { pattern?: unknown }).pattern !== undefined) {
139
+ throw new Error(
140
+ `${label} cannot declare a pattern; validate the format in the resolver.`
141
+ );
142
+ }
143
+ if (
144
+ typeof spec.description !== 'string' ||
145
+ spec.description.trim().length === 0 ||
146
+ spec.description.length > SUBAGENT_HOST_ARG_LIMITS.descriptionLength
147
+ ) {
148
+ throw new Error(
149
+ `${label} needs a description of at most ${SUBAGENT_HOST_ARG_LIMITS.descriptionLength} characters.`
150
+ );
151
+ }
152
+ if (spec.enum == null) {
153
+ validateFreeForm(label, spec);
154
+ return;
155
+ }
156
+ if (spec.maxLength != null) {
157
+ throw new Error(`${label} cannot combine enum with maxLength.`);
158
+ }
159
+ validateEnum(label, spec.enum);
160
+ }
161
+
162
+ /**
163
+ * Returns a subagent's validated host argument declarations in declaration
164
+ * order. Throws on a malformed or oversized declaration.
165
+ */
166
+ export function getSubagentHostArgSpecs(
167
+ config: SubagentHostArgDeclaration
168
+ ): readonly HostArgEntry[] {
169
+ const { hostArgs } = config;
170
+ if (hostArgs == null) {
171
+ return [];
172
+ }
173
+ if (typeof hostArgs !== 'object' || Array.isArray(hostArgs)) {
174
+ throw new Error(`Subagent "${config.type}" hostArgs must be an object.`);
175
+ }
176
+ const entries = Object.entries(hostArgs);
177
+ if (entries.length > SUBAGENT_HOST_ARG_LIMITS.argsPerSubagent) {
178
+ throw new Error(
179
+ `Subagent "${config.type}" declares more than ${SUBAGENT_HOST_ARG_LIMITS.argsPerSubagent} host arguments.`
180
+ );
181
+ }
182
+ for (const [name, spec] of entries) {
183
+ validateSpec(config.type, name, spec);
184
+ }
185
+ return entries;
186
+ }
187
+
188
+ /** Every host argument name declared by at least one subagent. */
189
+ export function collectSubagentHostArgNames(
190
+ configs: readonly SubagentHostArgDeclaration[]
191
+ ): ReadonlySet<string> {
192
+ const names = new Set<string>();
193
+ for (const config of configs) {
194
+ for (const [name] of getSubagentHostArgSpecs(config)) {
195
+ names.add(name);
196
+ }
197
+ }
198
+ if (names.size > SUBAGENT_HOST_ARG_LIMITS.distinctArgs) {
199
+ throw new Error(
200
+ `Subagents declare more than ${SUBAGENT_HOST_ARG_LIMITS.distinctArgs} distinct host arguments.`
201
+ );
202
+ }
203
+ return names;
204
+ }
205
+
206
+ function mergeSpec(
207
+ merged: Map<string, MergedHostArg>,
208
+ name: string,
209
+ spec: SubagentHostArgSpec
210
+ ): void {
211
+ let entry = merged.get(name);
212
+ if (entry == null) {
213
+ entry = {
214
+ description: spec.description,
215
+ values: [],
216
+ seenValues: new Set(),
217
+ freeForm: false,
218
+ maxLength: 0,
219
+ };
220
+ merged.set(name, entry);
221
+ }
222
+ if (spec.enum == null) {
223
+ entry.freeForm = true;
224
+ entry.maxLength = Math.max(entry.maxLength, getFreeFormMaxLength(spec));
225
+ return;
226
+ }
227
+ for (const value of spec.enum) {
228
+ entry.maxLength = Math.max(entry.maxLength, countCodePoints(value));
229
+ if (!entry.seenValues.has(value)) {
230
+ entry.seenValues.add(value);
231
+ entry.values.push(value);
232
+ }
233
+ }
234
+ if (entry.values.length > SUBAGENT_HOST_ARG_LIMITS.mergedEnumValues) {
235
+ throw new Error(
236
+ `Subagent host argument "${name}" lists more than ${SUBAGENT_HOST_ARG_LIMITS.mergedEnumValues} distinct values across subagents.`
237
+ );
238
+ }
239
+ }
240
+
241
+ function toPropertySchema(entry: MergedHostArg): JsonSchemaType {
242
+ if (!entry.freeForm) {
243
+ return {
244
+ type: 'string',
245
+ description: entry.description,
246
+ enum: entry.values,
247
+ };
248
+ }
249
+ return {
250
+ type: 'string',
251
+ description: entry.description,
252
+ maxLength: entry.maxLength,
253
+ pattern: FREE_FORM_SCHEMA_PATTERN,
254
+ };
255
+ }
256
+
257
+ function summarizeSpecs(entries: readonly HostArgEntry[]): string {
258
+ return entries
259
+ .map(
260
+ ([name, spec]) =>
261
+ `${name}: ${
262
+ spec.enum == null
263
+ ? `any text up to ${getFreeFormMaxLength(spec)} characters`
264
+ : spec.enum.join(' | ')
265
+ }`
266
+ )
267
+ .join('; ');
268
+ }
269
+
270
+ /**
271
+ * Builds one optional tool property per declared host argument plus a
272
+ * per-type summary of the values each subagent accepts. The first
273
+ * declaration's description wins; enums are unioned across subagents.
274
+ */
275
+ export function buildSubagentHostArgProperties(
276
+ configs: readonly SubagentHostArgDeclaration[]
277
+ ): {
278
+ properties: Record<string, JsonSchemaType>;
279
+ summaries: ReadonlyMap<string, string>;
280
+ } {
281
+ const merged = new Map<string, MergedHostArg>();
282
+ const summaries = new Map<string, string>();
283
+ for (const config of configs) {
284
+ const entries = getSubagentHostArgSpecs(config);
285
+ if (entries.length === 0) {
286
+ continue;
287
+ }
288
+ for (const [name, spec] of entries) {
289
+ mergeSpec(merged, name, spec);
290
+ }
291
+ summaries.set(config.type, summarizeSpecs(entries));
292
+ }
293
+ if (merged.size > SUBAGENT_HOST_ARG_LIMITS.distinctArgs) {
294
+ throw new Error(
295
+ `Subagents declare more than ${SUBAGENT_HOST_ARG_LIMITS.distinctArgs} distinct host arguments.`
296
+ );
297
+ }
298
+ const properties: Record<string, JsonSchemaType> = {};
299
+ for (const [name, entry] of merged) {
300
+ properties[name] = toPropertySchema(entry);
301
+ }
302
+ return { properties, summaries };
303
+ }
304
+
305
+ /**
306
+ * Reads the declared host argument properties from raw tool input. Missing,
307
+ * null, and blank values count as omitted; other non-string values are
308
+ * rejected rather than coerced.
309
+ */
310
+ export function pickSubagentHostArgInput(
311
+ input: object,
312
+ names: ReadonlySet<string>
313
+ ): SubagentHostArgsResult {
314
+ if (names.size === 0) {
315
+ return { ok: true };
316
+ }
317
+ const values = input as Readonly<Record<string, unknown>>;
318
+ const picked: Record<string, string> = {};
319
+ let count = 0;
320
+ for (const name of names) {
321
+ if (!Object.prototype.hasOwnProperty.call(values, name)) {
322
+ continue;
323
+ }
324
+ const value = values[name];
325
+ if (value == null) {
326
+ continue;
327
+ }
328
+ if (typeof value !== 'string') {
329
+ return { ok: false, message: `Error: "${name}" must be a string.` };
330
+ }
331
+ if (value.trim() === '') {
332
+ continue;
333
+ }
334
+ picked[name] = value;
335
+ count += 1;
336
+ }
337
+ return count === 0 ? { ok: true } : { ok: true, hostArgs: picked };
338
+ }
339
+
340
+ function checkValue(
341
+ type: string,
342
+ name: string,
343
+ spec: SubagentHostArgSpec,
344
+ value: string
345
+ ): string | undefined {
346
+ const omit = `Omit "${name}" to let the host choose.`;
347
+ if (spec.enum != null) {
348
+ return spec.enum.includes(value)
349
+ ? undefined
350
+ : `Error: "${name}" for subagent "${type}" must be one of: ${spec.enum.join(', ')}. ${omit}`;
351
+ }
352
+ const maxLength = getFreeFormMaxLength(spec);
353
+ return isBoundedText(value, maxLength)
354
+ ? undefined
355
+ : `Error: "${name}" for subagent "${type}" must be at most ${maxLength} characters without control characters. ${omit}`;
356
+ }
357
+
358
+ /**
359
+ * Checks call values against the selected subagent's declarations and
360
+ * returns them frozen in canonical key order. Rejects arguments the selected
361
+ * subagent does not declare, including ones declared by a sibling.
362
+ */
363
+ export function resolveSubagentHostArgs(
364
+ config: SubagentHostArgDeclaration,
365
+ hostArgs: SubagentHostArgs | undefined
366
+ ): SubagentHostArgsResult {
367
+ if (hostArgs == null) {
368
+ return { ok: true };
369
+ }
370
+ const names = Object.keys(hostArgs).sort();
371
+ if (names.length === 0) {
372
+ return { ok: true };
373
+ }
374
+ const specs = new Map(getSubagentHostArgSpecs(config));
375
+ const resolved: Record<string, string> = {};
376
+ for (const name of names) {
377
+ const spec = specs.get(name);
378
+ if (spec == null) {
379
+ return {
380
+ ok: false,
381
+ message: isSubagentHostArgName(name)
382
+ ? `Error: Subagent "${config.type}" does not accept "${name}". Omit it for this subagent type.`
383
+ : `Error: Subagent "${config.type}" received an unsupported argument.`,
384
+ };
385
+ }
386
+ const value = hostArgs[name];
387
+ if (typeof value !== 'string') {
388
+ return { ok: false, message: `Error: "${name}" must be a string.` };
389
+ }
390
+ const error = checkValue(config.type, name, spec, value);
391
+ if (error != null) {
392
+ return { ok: false, message: error };
393
+ }
394
+ resolved[name] = value;
395
+ }
396
+ return { ok: true, hostArgs: Object.freeze(resolved) };
397
+ }
398
+
399
+ /** Opaque, value-free identity of a call's host arguments for replay records. */
400
+ export function getSubagentHostArgsDigest(
401
+ hostArgs: SubagentHostArgs | undefined
402
+ ): string | undefined {
403
+ if (hostArgs == null) {
404
+ return undefined;
405
+ }
406
+ const entries = Object.entries(hostArgs).sort(([left], [right]) =>
407
+ left < right ? -1 : 1
408
+ );
409
+ if (entries.length === 0) {
410
+ return undefined;
411
+ }
412
+ return createHash('sha256').update(JSON.stringify(entries)).digest('hex');
413
+ }
@@ -21,9 +21,18 @@ export { InMemorySubagentTaskStore } from './InMemorySubagentTaskStore';
21
21
  export type { InMemorySubagentTaskStoreOptions } from './InMemorySubagentTaskStore';
22
22
  export {
23
23
  SubagentResolutionError,
24
+ SubagentHostArgumentError,
24
25
  getSubagentResolutionFailureMessage,
26
+ getSubagentHostArgumentFailureMessage,
25
27
  } from './diagnostics';
28
+ export {
29
+ SUBAGENT_HOST_ARG_LIMITS,
30
+ buildSubagentHostArgProperties,
31
+ resolveSubagentHostArgs,
32
+ } from './hostArgs';
26
33
  export type {
34
+ SubagentHostArgumentFailure,
35
+ SubagentHostArgumentRejection,
27
36
  SubagentResolutionPhase,
28
37
  SubagentResolutionCause,
29
38
  SubagentResolutionContext,
@@ -1,3 +1,4 @@
1
+ import type { ProviderTextProtection } from '@/protection/providerText';
1
2
  // src/types/graph.ts
2
3
  import type {
3
4
  BaseMessage,
@@ -408,6 +409,7 @@ export type ModelEndData =
408
409
  | undefined;
409
410
  export type GraphTools = GenericTool[] | BindToolsInput[] | GoogleAIToolType[];
410
411
  export type StandardGraphInput = {
412
+ providerTextProtection?: ProviderTextProtection;
411
413
  runId?: string;
412
414
  signal?: AbortSignal;
413
415
  agents: AgentInputs[];
@@ -574,6 +576,31 @@ export interface SubagentResolveConfigurable {
574
576
  user_id?: string;
575
577
  }
576
578
 
579
+ /**
580
+ * Optional string argument a host lets the parent model pass on a subagent
581
+ * call, such as where the child should run. The SDK checks the declared shape
582
+ * before resolution; the host still authorizes the value in its resolver.
583
+ */
584
+ export interface SubagentHostArgSpec {
585
+ /** Model-facing explanation of the argument and when to pass it. */
586
+ description: string;
587
+ /**
588
+ * Values this subagent accepts. Omit to accept a bounded free-form string,
589
+ * whose format the resolver must check.
590
+ */
591
+ enum?: readonly string[];
592
+ /** Maximum length of a free-form value (at most 256). */
593
+ maxLength?: number;
594
+ }
595
+
596
+ /** Host argument declarations keyed by tool-call property name. */
597
+ export type SubagentHostArgSpecs = Readonly<
598
+ Record<string, SubagentHostArgSpec>
599
+ >;
600
+
601
+ /** Validated host argument values from one subagent call. */
602
+ export type SubagentHostArgs = Readonly<Record<string, string>>;
603
+
577
604
  /** Runtime context supplied when a host lazily resolves a selected subagent. */
578
605
  export interface SubagentResolveContext {
579
606
  /** Stable subagent identity selected by the model. */
@@ -592,6 +619,13 @@ export interface SubagentResolveContext {
592
619
  signal: AbortSignal;
593
620
  /** Stable, sanitized host context from the parent tool invocation. */
594
621
  configurable?: Readonly<SubagentResolveConfigurable>;
622
+ /**
623
+ * Values the parent passed for this subagent's declared `hostArgs`, already
624
+ * checked against the declaration. Omitted when the call passed none. Bound
625
+ * to the execution: a reconstruction with different values is rejected
626
+ * before the resolver runs.
627
+ */
628
+ hostArgs?: SubagentHostArgs;
595
629
  }
596
630
 
597
631
  /** Host contract for resolving a selected subagent's full graph inputs. */
@@ -623,6 +657,12 @@ export interface SubagentConfig extends SubagentConfigBase {
623
657
  * `(context.executionId, context.descriptor.configId)`.
624
658
  */
625
659
  resolveAgentInputs?: SubagentAgentInputsResolver;
660
+ /**
661
+ * Optional per-call arguments the parent model may pass for this subagent,
662
+ * delivered to `resolveAgentInputs` as `context.hostArgs`. Only lazy
663
+ * configs accept them. Declaring none leaves the tool schema unchanged.
664
+ */
665
+ hostArgs?: SubagentHostArgSpecs;
626
666
  /** When true, reuse the parent's AgentInputs (context isolation without separate config). */
627
667
  self?: boolean;
628
668
  }
@@ -639,6 +679,7 @@ export interface GraphSubagentConfig extends SubagentConfigBase {
639
679
  kind: 'graph';
640
680
  configId?: never;
641
681
  resolveAgentInputs?: never;
682
+ hostArgs?: never;
642
683
  allowNested?: false;
643
684
  agents: AgentInputs[];
644
685
  /**
package/src/types/run.ts CHANGED
@@ -33,8 +33,9 @@ export type LegacyGraphConfig = BaseGraphConfig & {
33
33
  g.StandardGraphInput,
34
34
  /** `streamLimits` is excluded because legacy graphs receive limits only
35
35
  * via the top-level `RunConfig.streamLimits`; accepting the field here
36
- * would type-check but be silently ignored by `createLegacyGraph`. */
37
- 'provider' | 'clientOptions' | 'agents' | 'streamLimits'
36
+ * would type-check but be silently ignored by `createLegacyGraph`.
37
+ * Required provider-text protection also belongs only to RunConfig. */
38
+ 'provider' | 'clientOptions' | 'agents' | 'streamLimits' | 'providerTextProtection'
38
39
  > &
39
40
  Omit<g.AgentInputs, 'provider' | 'clientOptions' | 'agentId'>;
40
41
 
@@ -336,6 +337,7 @@ export type RunConfig = {
336
337
  * tool-call argument byte cap is ON by default, the per-turn event cap is
337
338
  * opt-in. See {@link StreamLimits}.
338
339
  */
340
+ providerTextProtection?: g.StandardGraphInput['providerTextProtection'];
339
341
  streamLimits?: StreamLimits;
340
342
  returnContent?: boolean;
341
343
  tokenCounter?: TokenCounter;