@langchain/langgraph-sdk 1.9.4 → 1.9.6

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 (110) hide show
  1. package/dist/client/base.cjs.map +1 -1
  2. package/dist/client/base.d.cts +37 -0
  3. package/dist/client/base.d.cts.map +1 -1
  4. package/dist/client/base.d.ts +37 -0
  5. package/dist/client/base.d.ts.map +1 -1
  6. package/dist/client/base.js.map +1 -1
  7. package/dist/client/index.d.cts +1 -1
  8. package/dist/client/index.d.ts +1 -1
  9. package/dist/client/stream/handles/index.d.ts +1 -1
  10. package/dist/client/stream/handles/subagents.cjs +1 -1
  11. package/dist/client/stream/handles/subagents.cjs.map +1 -1
  12. package/dist/client/stream/handles/subagents.d.cts +2 -2
  13. package/dist/client/stream/handles/subagents.d.cts.map +1 -1
  14. package/dist/client/stream/handles/subagents.d.ts +2 -2
  15. package/dist/client/stream/handles/subagents.d.ts.map +1 -1
  16. package/dist/client/stream/handles/subagents.js +2 -2
  17. package/dist/client/stream/handles/subagents.js.map +1 -1
  18. package/dist/client/stream/handles/subgraphs.cjs +1 -1
  19. package/dist/client/stream/handles/subgraphs.cjs.map +1 -1
  20. package/dist/client/stream/handles/subgraphs.d.cts +2 -2
  21. package/dist/client/stream/handles/subgraphs.d.cts.map +1 -1
  22. package/dist/client/stream/handles/subgraphs.d.ts +2 -2
  23. package/dist/client/stream/handles/subgraphs.d.ts.map +1 -1
  24. package/dist/client/stream/handles/subgraphs.js +2 -2
  25. package/dist/client/stream/handles/subgraphs.js.map +1 -1
  26. package/dist/client/stream/handles/tools.cjs +124 -52
  27. package/dist/client/stream/handles/tools.cjs.map +1 -1
  28. package/dist/client/stream/handles/tools.d.cts +59 -13
  29. package/dist/client/stream/handles/tools.d.cts.map +1 -1
  30. package/dist/client/stream/handles/tools.d.ts +59 -13
  31. package/dist/client/stream/handles/tools.d.ts.map +1 -1
  32. package/dist/client/stream/handles/tools.js +122 -53
  33. package/dist/client/stream/handles/tools.js.map +1 -1
  34. package/dist/client/stream/index.cjs +13 -5
  35. package/dist/client/stream/index.cjs.map +1 -1
  36. package/dist/client/stream/index.d.cts +3 -3
  37. package/dist/client/stream/index.d.cts.map +1 -1
  38. package/dist/client/stream/index.d.ts +3 -3
  39. package/dist/client/stream/index.d.ts.map +1 -1
  40. package/dist/client/stream/index.js +14 -6
  41. package/dist/client/stream/index.js.map +1 -1
  42. package/dist/headless-tools.cjs +131 -4
  43. package/dist/headless-tools.cjs.map +1 -1
  44. package/dist/headless-tools.d.cts +9 -1
  45. package/dist/headless-tools.d.cts.map +1 -1
  46. package/dist/headless-tools.d.ts +9 -1
  47. package/dist/headless-tools.d.ts.map +1 -1
  48. package/dist/headless-tools.js +129 -5
  49. package/dist/headless-tools.js.map +1 -1
  50. package/dist/index.cjs +1 -0
  51. package/dist/index.d.cts +3 -3
  52. package/dist/index.d.ts +3 -3
  53. package/dist/index.js +2 -2
  54. package/dist/stream/controller.cjs +77 -16
  55. package/dist/stream/controller.cjs.map +1 -1
  56. package/dist/stream/controller.d.cts +3 -1
  57. package/dist/stream/controller.d.cts.map +1 -1
  58. package/dist/stream/controller.d.ts +3 -1
  59. package/dist/stream/controller.d.ts.map +1 -1
  60. package/dist/stream/controller.js +78 -17
  61. package/dist/stream/controller.js.map +1 -1
  62. package/dist/stream/discovery/subagents.cjs +13 -0
  63. package/dist/stream/discovery/subagents.cjs.map +1 -1
  64. package/dist/stream/discovery/subagents.d.cts +5 -0
  65. package/dist/stream/discovery/subagents.d.cts.map +1 -1
  66. package/dist/stream/discovery/subagents.d.ts +5 -0
  67. package/dist/stream/discovery/subagents.d.ts.map +1 -1
  68. package/dist/stream/discovery/subagents.js +13 -0
  69. package/dist/stream/discovery/subagents.js.map +1 -1
  70. package/dist/stream/discovery/subgraphs.cjs +13 -0
  71. package/dist/stream/discovery/subgraphs.cjs.map +1 -1
  72. package/dist/stream/discovery/subgraphs.d.cts +5 -0
  73. package/dist/stream/discovery/subgraphs.d.cts.map +1 -1
  74. package/dist/stream/discovery/subgraphs.d.ts +5 -0
  75. package/dist/stream/discovery/subgraphs.d.ts.map +1 -1
  76. package/dist/stream/discovery/subgraphs.js +13 -0
  77. package/dist/stream/discovery/subgraphs.js.map +1 -1
  78. package/dist/stream/index.cjs +3 -0
  79. package/dist/stream/index.d.cts +5 -5
  80. package/dist/stream/index.d.ts +5 -5
  81. package/dist/stream/index.js +2 -1
  82. package/dist/stream/lifecycle-loading-tracker.cjs +1 -1
  83. package/dist/stream/lifecycle-loading-tracker.cjs.map +1 -1
  84. package/dist/stream/lifecycle-loading-tracker.js +1 -1
  85. package/dist/stream/lifecycle-loading-tracker.js.map +1 -1
  86. package/dist/stream/projections/tool-calls.cjs.map +1 -1
  87. package/dist/stream/projections/tool-calls.js.map +1 -1
  88. package/dist/stream/submit-coordinator.cjs +47 -16
  89. package/dist/stream/submit-coordinator.cjs.map +1 -1
  90. package/dist/stream/submit-coordinator.d.cts.map +1 -1
  91. package/dist/stream/submit-coordinator.d.ts.map +1 -1
  92. package/dist/stream/submit-coordinator.js +47 -16
  93. package/dist/stream/submit-coordinator.js.map +1 -1
  94. package/dist/stream/tool-calls.cjs +39 -2
  95. package/dist/stream/tool-calls.cjs.map +1 -1
  96. package/dist/stream/tool-calls.js +38 -3
  97. package/dist/stream/tool-calls.js.map +1 -1
  98. package/dist/stream/types-inference.d.cts +65 -7
  99. package/dist/stream/types-inference.d.cts.map +1 -1
  100. package/dist/stream/types-inference.d.ts +65 -7
  101. package/dist/stream/types-inference.d.ts.map +1 -1
  102. package/dist/stream/types.d.cts +42 -23
  103. package/dist/stream/types.d.cts.map +1 -1
  104. package/dist/stream/types.d.ts +42 -23
  105. package/dist/stream/types.d.ts.map +1 -1
  106. package/dist/types.messages.d.cts +38 -1
  107. package/dist/types.messages.d.cts.map +1 -1
  108. package/dist/types.messages.d.ts +38 -1
  109. package/dist/types.messages.d.ts.map +1 -1
  110. package/package.json +1 -1
@@ -100,6 +100,109 @@ function headlessToolResumeCommand(result) {
100
100
  return { resume: result.toolCallId ? { [result.toolCallId]: result.value } : result.value };
101
101
  }
102
102
  /**
103
+ * Merge headless-tool results into one resume command. Use interrupt-id keys
104
+ * whenever the stream provided them so the resume does not need to rediscover
105
+ * a pending interrupt from mutable client state.
106
+ */
107
+ function headlessToolsBatchResumeCommand(entries) {
108
+ if (entries.length === 0) return { resume: {} };
109
+ if (!entries.every((entry) => entry.interruptId.length > 0) && entries.length === 1) {
110
+ const [entry] = entries;
111
+ return headlessToolResumeCommand({
112
+ toolCallId: entry.toolCallId,
113
+ value: entry.value
114
+ });
115
+ }
116
+ const resume = {};
117
+ for (const entry of entries) {
118
+ if (entry.interruptId.length === 0) continue;
119
+ resume[entry.interruptId] = entry.toolCallId != null && entry.toolCallId.length > 0 ? { [entry.toolCallId]: entry.value } : entry.value;
120
+ }
121
+ return { resume };
122
+ }
123
+ /**
124
+ * True when every top-level resume key is a graph task interrupt id
125
+ * (32-char hex from `values.__interrupt__`).
126
+ */
127
+ function isInterruptIdKeyedResume(resume) {
128
+ if (resume == null || typeof resume !== "object" || Array.isArray(resume)) return false;
129
+ const keys = Object.keys(resume);
130
+ if (keys.length === 0) return false;
131
+ return keys.every((key) => /^[0-9a-f]{32}$/i.test(key));
132
+ }
133
+ /**
134
+ * Normalize `command.resume` into the `run.start` input the API turns
135
+ * into `Command({ resume })`. Interrupt-id keyed payloads pass through;
136
+ * tool-call-keyed and generic payloads are wrapped under the matching
137
+ * protocol interrupt id.
138
+ */
139
+ function buildResumeRunInput(resume, interrupts, resolvedInterruptIds) {
140
+ if (resume == null) return null;
141
+ if (isInterruptIdKeyedResume(resume)) return resume;
142
+ const target = resolveInterruptTargetForHeadlessResume(resume, interrupts, resolvedInterruptIds);
143
+ if (target == null) return null;
144
+ return { [target.interruptId]: resume };
145
+ }
146
+ /**
147
+ * Reads the tool-call id from a headless-tool resume command shaped as
148
+ * `{ [toolCallId]: result }`.
149
+ */
150
+ function extractHeadlessToolCallIdFromResumeCommand(resume) {
151
+ if (resume == null || typeof resume !== "object" || Array.isArray(resume)) return;
152
+ const keys = Object.keys(resume);
153
+ if (keys.length !== 1) return void 0;
154
+ return keys[0];
155
+ }
156
+ /**
157
+ * Pick the protocol interrupt that matches a headless-tool resume payload.
158
+ * Falls back to the newest unresolved interrupt for non-keyed resumes.
159
+ */
160
+ function resolveInterruptTargetForHeadlessResume(resume, interrupts, resolvedInterruptIds) {
161
+ const toolCallId = extractHeadlessToolCallIdFromResumeCommand(resume);
162
+ if (toolCallId != null) for (let i = interrupts.length - 1; i >= 0; i -= 1) {
163
+ const entry = interrupts[i];
164
+ if (entry == null || resolvedInterruptIds.has(entry.interruptId)) continue;
165
+ if (parseHeadlessToolInterruptPayload(entry.payload)?.toolCall.id === toolCallId) return {
166
+ interruptId: entry.interruptId,
167
+ namespace: [...entry.namespace]
168
+ };
169
+ }
170
+ for (let i = interrupts.length - 1; i >= 0; i -= 1) {
171
+ const entry = interrupts[i];
172
+ if (entry == null || resolvedInterruptIds.has(entry.interruptId)) continue;
173
+ return {
174
+ interruptId: entry.interruptId,
175
+ namespace: [...entry.namespace]
176
+ };
177
+ }
178
+ return null;
179
+ }
180
+ const coalescedHeadlessFlushes = /* @__PURE__ */ new WeakMap();
181
+ /**
182
+ * Coalesce rapid headless-tool flush triggers into one microtask so parallel
183
+ * `input.requested` events observed back-to-back batch into a single resume.
184
+ * Vue/Svelte/Angular watchers run synchronously per event; without this,
185
+ * the first interrupt can be claimed before the second arrives and resume
186
+ * splits into staggered single-tool commands.
187
+ */
188
+ function scheduleCoalescedHeadlessToolFlush(handledIds, run) {
189
+ let state = coalescedHeadlessFlushes.get(handledIds);
190
+ if (state == null) {
191
+ state = {
192
+ scheduled: false,
193
+ run: () => {}
194
+ };
195
+ coalescedHeadlessFlushes.set(handledIds, state);
196
+ }
197
+ state.run = run;
198
+ if (state.scheduled) return;
199
+ state.scheduled = true;
200
+ Promise.resolve().then(() => {
201
+ state.scheduled = false;
202
+ state.run();
203
+ });
204
+ }
205
+ /**
103
206
  * Execute and resume all newly seen headless-tool interrupts from a values
104
207
  * payload. Callers own `handledIds` and should clear it when the thread changes.
105
208
  */
@@ -108,20 +211,42 @@ function flushPendingHeadlessToolInterrupts(values, tools, handledIds, options)
108
211
  const interrupts = values.__interrupt__;
109
212
  if (!Array.isArray(interrupts) || interrupts.length === 0) return;
110
213
  const defer = options.defer ?? ((run) => run());
214
+ const pending = [];
215
+ const seenToolCallIds = /* @__PURE__ */ new Set();
111
216
  for (const interrupt of interrupts) {
112
217
  const headlessInterrupt = parseHeadlessToolInterruptPayload(interrupt.value);
113
218
  if (!headlessInterrupt) continue;
114
219
  const interruptId = interrupt.id ?? headlessInterrupt.toolCall.id ?? "";
220
+ const toolCallId = headlessInterrupt.toolCall.id ?? "";
115
221
  if (handledIds.has(interruptId)) continue;
222
+ if (toolCallId && handledIds.has(toolCallId)) continue;
223
+ if (toolCallId && seenToolCallIds.has(toolCallId)) continue;
224
+ if (toolCallId) seenToolCallIds.add(toolCallId);
116
225
  handledIds.add(interruptId);
117
- defer(() => {
118
- handleHeadlessToolInterrupt(headlessInterrupt, tools, options.onTool).then((result) => {
119
- options.resumeSubmit(headlessToolResumeCommand(result));
120
- });
226
+ if (toolCallId) handledIds.add(toolCallId);
227
+ pending.push({
228
+ interruptId,
229
+ headlessInterrupt,
230
+ toolCallId
121
231
  });
122
232
  }
233
+ if (pending.length === 0) return;
234
+ defer(() => {
235
+ (async () => {
236
+ const results = await Promise.all(pending.map(async ({ interruptId, headlessInterrupt, toolCallId }) => {
237
+ const result = await handleHeadlessToolInterrupt(headlessInterrupt, tools, options.onTool);
238
+ return {
239
+ interruptId,
240
+ toolCallId: result.toolCallId ?? toolCallId,
241
+ value: result.value
242
+ };
243
+ }));
244
+ await Promise.resolve(options.resumeSubmit(headlessToolsBatchResumeCommand(results)));
245
+ })();
246
+ });
123
247
  }
124
248
  //#endregion
249
+ exports.buildResumeRunInput = buildResumeRunInput;
125
250
  exports.executeHeadlessTool = executeHeadlessTool;
126
251
  exports.filterOutHeadlessToolInterrupts = filterOutHeadlessToolInterrupts;
127
252
  exports.findHeadlessTool = findHeadlessTool;
@@ -130,5 +255,7 @@ exports.handleHeadlessToolInterrupt = handleHeadlessToolInterrupt;
130
255
  exports.headlessToolResumeCommand = headlessToolResumeCommand;
131
256
  exports.isHeadlessToolInterrupt = isHeadlessToolInterrupt;
132
257
  exports.parseHeadlessToolInterruptPayload = parseHeadlessToolInterruptPayload;
258
+ exports.resolveInterruptTargetForHeadlessResume = resolveInterruptTargetForHeadlessResume;
259
+ exports.scheduleCoalescedHeadlessToolFlush = scheduleCoalescedHeadlessToolFlush;
133
260
 
134
261
  //# sourceMappingURL=headless-tools.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"headless-tools.cjs","names":[],"sources":["../src/headless-tools.ts"],"sourcesContent":["import type { Interrupt } from \"./schema.js\";\n\n/**\n * Represents a headless tool interrupt payload emitted by LangChain's\n * schema-only `tool({ ... })` overload.\n *\n * Servers may serialize the nested tool call as `toolCall` (JS) or\n * `tool_call` (Python). Use {@link parseHeadlessToolInterruptPayload} to\n * normalize either shape before reading fields.\n */\nexport interface HeadlessToolInterrupt {\n type: \"tool\";\n toolCall: {\n id: string | undefined;\n name: string;\n args: unknown;\n };\n}\n\n/**\n * Parses a headless-tool interrupt `value` from the graph. Accepts both\n * `toolCall` (LangChain JS) and `tool_call` (Python / JSON snake_case).\n */\nexport function parseHeadlessToolInterruptPayload(\n value: unknown\n): HeadlessToolInterrupt | null {\n if (typeof value !== \"object\" || value == null) {\n return null;\n }\n const v = value as Record<string, unknown>;\n if (v.type !== \"tool\") {\n return null;\n }\n\n const rawTc = v.toolCall ?? v.tool_call;\n if (typeof rawTc !== \"object\" || rawTc == null) {\n return null;\n }\n const tc = rawTc as Record<string, unknown>;\n if (typeof tc.name !== \"string\") {\n return null;\n }\n\n const id = typeof tc.id === \"string\" ? tc.id : undefined;\n\n return {\n type: \"tool\",\n toolCall: {\n id,\n name: tc.name,\n args: tc.args,\n },\n };\n}\n\n/**\n * Client-side implementation returned by `headlessTool.implement(...)`.\n */\nexport interface HeadlessToolImplementation<Args = unknown, Output = unknown> {\n tool: {\n name: string;\n };\n execute: (args: Args) => Promise<Output>;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyHeadlessToolImplementation = HeadlessToolImplementation<\n any,\n any\n>;\n\nexport interface ToolEvent {\n phase: \"start\" | \"success\" | \"error\";\n name: string;\n args: unknown;\n result?: unknown;\n error?: Error;\n duration?: number;\n}\n\nexport type OnToolCallback = (event: ToolEvent) => void;\n\n/**\n * Strip headless-tool interrupts from a user-facing interrupt list.\n */\nexport function filterOutHeadlessToolInterrupts<T extends { value?: unknown }>(\n interrupts: readonly T[]\n): T[] {\n return interrupts.filter(\n (interrupt) =>\n interrupt.value == null || !isHeadlessToolInterrupt(interrupt.value)\n );\n}\n\nexport function isHeadlessToolInterrupt(\n interrupt: unknown\n): interrupt is HeadlessToolInterrupt {\n return parseHeadlessToolInterruptPayload(interrupt) != null;\n}\n\nexport function findHeadlessTool<Args = unknown, Output = unknown>(\n tools: HeadlessToolImplementation[],\n name: string\n): HeadlessToolImplementation<Args, Output> | undefined {\n return tools.find((tool) => tool.tool.name === name) as\n | HeadlessToolImplementation<Args, Output>\n | undefined;\n}\n\nexport async function executeHeadlessTool<Args = unknown, Output = unknown>(\n implementation: HeadlessToolImplementation<Args, Output>,\n args: Args,\n onTool?: OnToolCallback\n): Promise<\n { success: true; result: Output } | { success: false; error: Error }\n> {\n const startTime = Date.now();\n\n onTool?.({\n phase: \"start\",\n name: implementation.tool.name,\n args,\n });\n\n try {\n const result = await implementation.execute(args);\n const duration = Date.now() - startTime;\n\n onTool?.({\n phase: \"success\",\n name: implementation.tool.name,\n args,\n result,\n duration,\n });\n\n return { success: true, result };\n } catch (err) {\n // oxlint-disable-next-line no-instanceof/no-instanceof\n const error = err instanceof Error ? err : new Error(String(err));\n const duration = Date.now() - startTime;\n\n onTool?.({\n phase: \"error\",\n name: implementation.tool.name,\n args,\n error,\n duration,\n });\n\n return { success: false, error };\n }\n}\n\nexport async function handleHeadlessToolInterrupt(\n interrupt: HeadlessToolInterrupt,\n tools: HeadlessToolImplementation[],\n onTool?: OnToolCallback\n): Promise<{ toolCallId: string | undefined; value: unknown }> {\n const { toolCall } = interrupt;\n const implementation = findHeadlessTool(tools, toolCall.name);\n\n if (!implementation) {\n const error = new Error(\n `Headless tool \"${toolCall.name}\" is not registered. ` +\n `Available tools: ${tools.map((tool) => tool.tool.name).join(\", \") || \"none\"}`\n );\n\n onTool?.({\n phase: \"error\",\n name: toolCall.name,\n args: toolCall.args,\n error,\n duration: 0,\n });\n\n return {\n toolCallId: toolCall.id,\n value: { error: error.message },\n };\n }\n\n const result = await executeHeadlessTool(\n implementation,\n toolCall.args as never,\n onTool\n );\n\n if (result.success) {\n return {\n toolCallId: toolCall.id,\n value: result.result,\n };\n }\n\n return {\n toolCallId: toolCall.id,\n value: { error: result.error.message },\n };\n}\n\nexport function headlessToolResumeCommand(result: {\n toolCallId: string | undefined;\n value: unknown;\n}): { resume: unknown } {\n return {\n resume: result.toolCallId\n ? { [result.toolCallId]: result.value }\n : result.value,\n };\n}\n\nexport interface FlushPendingHeadlessToolInterruptsOptions {\n onTool?: OnToolCallback;\n resumeSubmit: (command: { resume: unknown }) => void | Promise<void>;\n defer?: (run: () => void) => void;\n}\n\n/**\n * Execute and resume all newly seen headless-tool interrupts from a values\n * payload. Callers own `handledIds` and should clear it when the thread changes.\n */\nexport function flushPendingHeadlessToolInterrupts(\n values: Record<string, unknown> | null | undefined,\n tools: HeadlessToolImplementation[] | undefined,\n handledIds: Set<string>,\n options: FlushPendingHeadlessToolInterruptsOptions\n): void {\n if (!tools?.length || !values) return;\n\n const interrupts = values.__interrupt__;\n if (!Array.isArray(interrupts) || interrupts.length === 0) return;\n\n const defer = options.defer ?? ((run) => run());\n\n for (const interrupt of interrupts as Interrupt[]) {\n const headlessInterrupt = parseHeadlessToolInterruptPayload(\n interrupt.value\n );\n if (!headlessInterrupt) continue;\n\n const interruptId = interrupt.id ?? headlessInterrupt.toolCall.id ?? \"\";\n if (handledIds.has(interruptId)) continue;\n handledIds.add(interruptId);\n\n defer(() => {\n void handleHeadlessToolInterrupt(\n headlessInterrupt,\n tools,\n options.onTool\n ).then((result) => {\n void options.resumeSubmit(headlessToolResumeCommand(result));\n });\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"headless-tools.cjs","names":[],"sources":["../src/headless-tools.ts"],"sourcesContent":["import type { Interrupt } from \"./schema.js\";\n\n/**\n * Represents a headless tool interrupt payload emitted by LangChain's\n * schema-only `tool({ ... })` overload.\n *\n * Servers may serialize the nested tool call as `toolCall` (JS) or\n * `tool_call` (Python). Use {@link parseHeadlessToolInterruptPayload} to\n * normalize either shape before reading fields.\n */\nexport interface HeadlessToolInterrupt {\n type: \"tool\";\n toolCall: {\n id: string | undefined;\n name: string;\n args: unknown;\n };\n}\n\n/**\n * Parses a headless-tool interrupt `value` from the graph. Accepts both\n * `toolCall` (LangChain JS) and `tool_call` (Python / JSON snake_case).\n */\nexport function parseHeadlessToolInterruptPayload(\n value: unknown\n): HeadlessToolInterrupt | null {\n if (typeof value !== \"object\" || value == null) {\n return null;\n }\n const v = value as Record<string, unknown>;\n if (v.type !== \"tool\") {\n return null;\n }\n\n const rawTc = v.toolCall ?? v.tool_call;\n if (typeof rawTc !== \"object\" || rawTc == null) {\n return null;\n }\n const tc = rawTc as Record<string, unknown>;\n if (typeof tc.name !== \"string\") {\n return null;\n }\n\n const id = typeof tc.id === \"string\" ? tc.id : undefined;\n\n return {\n type: \"tool\",\n toolCall: {\n id,\n name: tc.name,\n args: tc.args,\n },\n };\n}\n\n/**\n * Client-side implementation returned by `headlessTool.implement(...)`.\n */\nexport interface HeadlessToolImplementation<Args = unknown, Output = unknown> {\n tool: {\n name: string;\n };\n execute: (args: Args) => Promise<Output>;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyHeadlessToolImplementation = HeadlessToolImplementation<\n any,\n any\n>;\n\nexport interface ToolEvent {\n phase: \"start\" | \"success\" | \"error\";\n name: string;\n args: unknown;\n result?: unknown;\n error?: Error;\n duration?: number;\n}\n\nexport type OnToolCallback = (event: ToolEvent) => void;\n\n/**\n * Strip headless-tool interrupts from a user-facing interrupt list.\n */\nexport function filterOutHeadlessToolInterrupts<T extends { value?: unknown }>(\n interrupts: readonly T[]\n): T[] {\n return interrupts.filter(\n (interrupt) =>\n interrupt.value == null || !isHeadlessToolInterrupt(interrupt.value)\n );\n}\n\nexport function isHeadlessToolInterrupt(\n interrupt: unknown\n): interrupt is HeadlessToolInterrupt {\n return parseHeadlessToolInterruptPayload(interrupt) != null;\n}\n\nexport function findHeadlessTool<Args = unknown, Output = unknown>(\n tools: HeadlessToolImplementation[],\n name: string\n): HeadlessToolImplementation<Args, Output> | undefined {\n return tools.find((tool) => tool.tool.name === name) as\n | HeadlessToolImplementation<Args, Output>\n | undefined;\n}\n\nexport async function executeHeadlessTool<Args = unknown, Output = unknown>(\n implementation: HeadlessToolImplementation<Args, Output>,\n args: Args,\n onTool?: OnToolCallback\n): Promise<\n { success: true; result: Output } | { success: false; error: Error }\n> {\n const startTime = Date.now();\n\n onTool?.({\n phase: \"start\",\n name: implementation.tool.name,\n args,\n });\n\n try {\n const result = await implementation.execute(args);\n const duration = Date.now() - startTime;\n\n onTool?.({\n phase: \"success\",\n name: implementation.tool.name,\n args,\n result,\n duration,\n });\n\n return { success: true, result };\n } catch (err) {\n // oxlint-disable-next-line no-instanceof/no-instanceof\n const error = err instanceof Error ? err : new Error(String(err));\n const duration = Date.now() - startTime;\n\n onTool?.({\n phase: \"error\",\n name: implementation.tool.name,\n args,\n error,\n duration,\n });\n\n return { success: false, error };\n }\n}\n\nexport async function handleHeadlessToolInterrupt(\n interrupt: HeadlessToolInterrupt,\n tools: HeadlessToolImplementation[],\n onTool?: OnToolCallback\n): Promise<{ toolCallId: string | undefined; value: unknown }> {\n const { toolCall } = interrupt;\n const implementation = findHeadlessTool(tools, toolCall.name);\n\n if (!implementation) {\n const error = new Error(\n `Headless tool \"${toolCall.name}\" is not registered. ` +\n `Available tools: ${tools.map((tool) => tool.tool.name).join(\", \") || \"none\"}`\n );\n\n onTool?.({\n phase: \"error\",\n name: toolCall.name,\n args: toolCall.args,\n error,\n duration: 0,\n });\n\n return {\n toolCallId: toolCall.id,\n value: { error: error.message },\n };\n }\n\n const result = await executeHeadlessTool(\n implementation,\n toolCall.args as never,\n onTool\n );\n\n if (result.success) {\n return {\n toolCallId: toolCall.id,\n value: result.result,\n };\n }\n\n return {\n toolCallId: toolCall.id,\n value: { error: result.error.message },\n };\n}\n\nexport function headlessToolResumeCommand(result: {\n toolCallId: string | undefined;\n value: unknown;\n}): { resume: unknown } {\n return {\n resume: result.toolCallId\n ? { [result.toolCallId]: result.value }\n : result.value,\n };\n}\n\n/**\n * Merge headless-tool results into one resume command. Use interrupt-id keys\n * whenever the stream provided them so the resume does not need to rediscover\n * a pending interrupt from mutable client state.\n */\nexport function headlessToolsBatchResumeCommand(\n entries: ReadonlyArray<{\n interruptId: string;\n toolCallId: string | undefined;\n value: unknown;\n }>\n): { resume: unknown } {\n if (entries.length === 0) {\n return { resume: {} };\n }\n\n const hasInterruptIds = entries.every(\n (entry) => entry.interruptId.length > 0\n );\n if (!hasInterruptIds && entries.length === 1) {\n const [entry] = entries;\n return headlessToolResumeCommand({\n toolCallId: entry.toolCallId,\n value: entry.value,\n });\n }\n\n const resume: Record<string, unknown> = {};\n for (const entry of entries) {\n if (entry.interruptId.length === 0) continue;\n resume[entry.interruptId] =\n entry.toolCallId != null && entry.toolCallId.length > 0\n ? { [entry.toolCallId]: entry.value }\n : entry.value;\n }\n return { resume };\n}\n\n/**\n * True when every top-level resume key is a graph task interrupt id\n * (32-char hex from `values.__interrupt__`).\n */\nexport function isInterruptIdKeyedResume(resume: unknown): boolean {\n if (resume == null || typeof resume !== \"object\" || Array.isArray(resume)) {\n return false;\n }\n const keys = Object.keys(resume as Record<string, unknown>);\n if (keys.length === 0) return false;\n return keys.every((key) => /^[0-9a-f]{32}$/i.test(key));\n}\n\n/**\n * Normalize `command.resume` into the `run.start` input the API turns\n * into `Command({ resume })`. Interrupt-id keyed payloads pass through;\n * tool-call-keyed and generic payloads are wrapped under the matching\n * protocol interrupt id.\n */\nexport function buildResumeRunInput(\n resume: unknown,\n interrupts: readonly ProtocolInterruptEntry[],\n resolvedInterruptIds: ReadonlySet<string>\n): Record<string, unknown> | null {\n if (resume == null) return null;\n if (isInterruptIdKeyedResume(resume)) {\n return resume as Record<string, unknown>;\n }\n\n const target = resolveInterruptTargetForHeadlessResume(\n resume,\n interrupts,\n resolvedInterruptIds\n );\n if (target == null) return null;\n\n return { [target.interruptId]: resume };\n}\n\n/**\n * Reads the tool-call id from a headless-tool resume command shaped as\n * `{ [toolCallId]: result }`.\n */\nexport function extractHeadlessToolCallIdFromResumeCommand(\n resume: unknown\n): string | undefined {\n if (resume == null || typeof resume !== \"object\" || Array.isArray(resume)) {\n return undefined;\n }\n const keys = Object.keys(resume as Record<string, unknown>);\n if (keys.length !== 1) return undefined;\n return keys[0];\n}\n\nexport interface ProtocolInterruptEntry {\n interruptId: string;\n namespace: string[];\n payload: unknown;\n}\n\n/**\n * Pick the protocol interrupt that matches a headless-tool resume payload.\n * Falls back to the newest unresolved interrupt for non-keyed resumes.\n */\nexport function resolveInterruptTargetForHeadlessResume(\n resume: unknown,\n interrupts: readonly ProtocolInterruptEntry[],\n resolvedInterruptIds: ReadonlySet<string>\n): { interruptId: string; namespace: string[] } | null {\n const toolCallId = extractHeadlessToolCallIdFromResumeCommand(resume);\n if (toolCallId != null) {\n for (let i = interrupts.length - 1; i >= 0; i -= 1) {\n const entry = interrupts[i];\n if (entry == null || resolvedInterruptIds.has(entry.interruptId)) {\n continue;\n }\n const headless = parseHeadlessToolInterruptPayload(entry.payload);\n if (headless?.toolCall.id === toolCallId) {\n return {\n interruptId: entry.interruptId,\n namespace: [...entry.namespace],\n };\n }\n }\n }\n\n for (let i = interrupts.length - 1; i >= 0; i -= 1) {\n const entry = interrupts[i];\n if (entry == null || resolvedInterruptIds.has(entry.interruptId)) {\n continue;\n }\n return {\n interruptId: entry.interruptId,\n namespace: [...entry.namespace],\n };\n }\n return null;\n}\n\nexport interface FlushPendingHeadlessToolInterruptsOptions {\n onTool?: OnToolCallback;\n resumeSubmit: (command: { resume: unknown }) => void | Promise<void>;\n defer?: (run: () => void) => void;\n}\n\nconst coalescedHeadlessFlushes = new WeakMap<\n Set<string>,\n { scheduled: boolean; run: () => void }\n>();\n\n/**\n * Coalesce rapid headless-tool flush triggers into one microtask so parallel\n * `input.requested` events observed back-to-back batch into a single resume.\n * Vue/Svelte/Angular watchers run synchronously per event; without this,\n * the first interrupt can be claimed before the second arrives and resume\n * splits into staggered single-tool commands.\n */\nexport function scheduleCoalescedHeadlessToolFlush(\n handledIds: Set<string>,\n run: () => void\n): void {\n let state = coalescedHeadlessFlushes.get(handledIds);\n if (state == null) {\n state = { scheduled: false, run: () => {} };\n coalescedHeadlessFlushes.set(handledIds, state);\n }\n state.run = run;\n if (state.scheduled) return;\n state.scheduled = true;\n void Promise.resolve().then(() => {\n state!.scheduled = false;\n state!.run();\n });\n}\n\n/**\n * Execute and resume all newly seen headless-tool interrupts from a values\n * payload. Callers own `handledIds` and should clear it when the thread changes.\n */\nexport function flushPendingHeadlessToolInterrupts(\n values: Record<string, unknown> | null | undefined,\n tools: HeadlessToolImplementation[] | undefined,\n handledIds: Set<string>,\n options: FlushPendingHeadlessToolInterruptsOptions\n): void {\n if (!tools?.length || !values) return;\n\n const interrupts = values.__interrupt__;\n if (!Array.isArray(interrupts) || interrupts.length === 0) return;\n\n const defer = options.defer ?? ((run) => run());\n const pending: Array<{\n interruptId: string;\n headlessInterrupt: HeadlessToolInterrupt;\n toolCallId: string;\n }> = [];\n const seenToolCallIds = new Set<string>();\n\n for (const interrupt of interrupts as Interrupt[]) {\n const headlessInterrupt = parseHeadlessToolInterruptPayload(\n interrupt.value\n );\n if (!headlessInterrupt) continue;\n\n const interruptId = interrupt.id ?? headlessInterrupt.toolCall.id ?? \"\";\n const toolCallId = headlessInterrupt.toolCall.id ?? \"\";\n if (handledIds.has(interruptId)) continue;\n // v2 protocol runs mirror the same headless-tool interrupt in both\n // `values.__interrupt__` and `rootStore.interrupts` with different\n // ids (graph/task id vs protocol interrupt_id). The headless-tool\n // effect can also re-run after the first resume clears\n // `rootStore.interrupts` while `values.__interrupt__` is still\n // present — persist tool call ids in the caller-owned set so we\n // only execute + resume once per pending tool call.\n if (toolCallId && handledIds.has(toolCallId)) continue;\n if (toolCallId && seenToolCallIds.has(toolCallId)) continue;\n if (toolCallId) seenToolCallIds.add(toolCallId);\n\n // Claim before defer so a second flush in the same tick cannot\n // schedule a duplicate execute/resume for the same interrupt.\n handledIds.add(interruptId);\n if (toolCallId) handledIds.add(toolCallId);\n\n pending.push({ interruptId, headlessInterrupt, toolCallId });\n }\n\n if (pending.length === 0) return;\n\n defer(() => {\n void (async () => {\n const results = await Promise.all(\n pending.map(async ({ interruptId, headlessInterrupt, toolCallId }) => {\n const result = await handleHeadlessToolInterrupt(\n headlessInterrupt,\n tools,\n options.onTool\n );\n return {\n interruptId,\n toolCallId: result.toolCallId ?? toolCallId,\n value: result.value,\n };\n })\n );\n await Promise.resolve(\n options.resumeSubmit(headlessToolsBatchResumeCommand(results))\n );\n })();\n });\n}\n"],"mappings":"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"}
@@ -71,11 +71,19 @@ interface FlushPendingHeadlessToolInterruptsOptions {
71
71
  }) => void | Promise<void>;
72
72
  defer?: (run: () => void) => void;
73
73
  }
74
+ /**
75
+ * Coalesce rapid headless-tool flush triggers into one microtask so parallel
76
+ * `input.requested` events observed back-to-back batch into a single resume.
77
+ * Vue/Svelte/Angular watchers run synchronously per event; without this,
78
+ * the first interrupt can be claimed before the second arrives and resume
79
+ * splits into staggered single-tool commands.
80
+ */
81
+ declare function scheduleCoalescedHeadlessToolFlush(handledIds: Set<string>, run: () => void): void;
74
82
  /**
75
83
  * Execute and resume all newly seen headless-tool interrupts from a values
76
84
  * payload. Callers own `handledIds` and should clear it when the thread changes.
77
85
  */
78
86
  declare function flushPendingHeadlessToolInterrupts(values: Record<string, unknown> | null | undefined, tools: HeadlessToolImplementation[] | undefined, handledIds: Set<string>, options: FlushPendingHeadlessToolInterruptsOptions): void;
79
87
  //#endregion
80
- export { AnyHeadlessToolImplementation, FlushPendingHeadlessToolInterruptsOptions, HeadlessToolImplementation, HeadlessToolInterrupt, OnToolCallback, ToolEvent, executeHeadlessTool, filterOutHeadlessToolInterrupts, findHeadlessTool, flushPendingHeadlessToolInterrupts, handleHeadlessToolInterrupt, headlessToolResumeCommand, isHeadlessToolInterrupt, parseHeadlessToolInterruptPayload };
88
+ export { AnyHeadlessToolImplementation, FlushPendingHeadlessToolInterruptsOptions, HeadlessToolImplementation, HeadlessToolInterrupt, OnToolCallback, ToolEvent, executeHeadlessTool, filterOutHeadlessToolInterrupts, findHeadlessTool, flushPendingHeadlessToolInterrupts, handleHeadlessToolInterrupt, headlessToolResumeCommand, isHeadlessToolInterrupt, parseHeadlessToolInterruptPayload, scheduleCoalescedHeadlessToolFlush };
81
89
  //# sourceMappingURL=headless-tools.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"headless-tools.d.cts","names":[],"sources":["../src/headless-tools.ts"],"mappings":";;AAUA;;;;;;;UAAiB,qBAAA;EACf,IAAA;EACA,QAAA;IACE,EAAA;IACA,IAAA;IACA,IAAA;EAAA;AAAA;;AA2CJ;;;iBAnCgB,iCAAA,CACd,KAAA,YACC,qBAAA;;;;UAiCc,0BAAA;EACf,IAAA;IACE,IAAA;EAAA;EAEF,OAAA,GAAU,IAAA,EAAM,IAAA,KAAS,OAAA,CAAQ,MAAA;AAAA;AAAA,KAIvB,6BAAA,GAAgC,0BAAA;AAAA,UAK3B,SAAA;EACf,KAAA;EACA,IAAA;EACA,IAAA;EACA,MAAA;EACA,KAAA,GAAQ,KAAA;EACR,QAAA;AAAA;AAAA,KAGU,cAAA,IAAkB,KAAA,EAAO,SAAA;;AATrC;;iBAcgB,+BAAA;EAA4C,KAAA;AAAA,EAAA,CAC1D,UAAA,WAAqB,CAAA,KACpB,CAAA;AAAA,iBAOa,uBAAA,CACd,SAAA,YACC,SAAA,IAAa,qBAAA;AAAA,iBAIA,gBAAA,kCAAA,CACd,KAAA,EAAO,0BAAA,IACP,IAAA,WACC,0BAAA,CAA2B,IAAA,EAAM,MAAA;AAAA,iBAMd,mBAAA,kCAAA,CACpB,cAAA,EAAgB,0BAAA,CAA2B,IAAA,EAAM,MAAA,GACjD,IAAA,EAAM,IAAA,EACN,MAAA,GAAS,cAAA,GACR,OAAA;EACC,OAAA;EAAe,MAAA,EAAQ,MAAA;AAAA;EAAa,OAAA;EAAgB,KAAA,EAAO,KAAA;AAAA;AAAA,iBAwCzC,2BAAA,CACpB,SAAA,EAAW,qBAAA,EACX,KAAA,EAAO,0BAAA,IACP,MAAA,GAAS,cAAA,GACR,OAAA;EAAU,UAAA;EAAgC,KAAA;AAAA;AAAA,iBA2C7B,yBAAA,CAA0B,MAAA;EACxC,UAAA;EACA,KAAA;AAAA;EACI,MAAA;AAAA;AAAA,UAQW,yCAAA;EACf,MAAA,GAAS,cAAA;EACT,YAAA,GAAe,OAAA;IAAW,MAAA;EAAA,aAA6B,OAAA;EACvD,KAAA,IAAS,GAAA;AAAA;;;;;iBAOK,kCAAA,CACd,MAAA,EAAQ,MAAA,sCACR,KAAA,EAAO,0BAAA,gBACP,UAAA,EAAY,GAAA,UACZ,OAAA,EAAS,yCAAA"}
1
+ {"version":3,"file":"headless-tools.d.cts","names":[],"sources":["../src/headless-tools.ts"],"mappings":";;AAUA;;;;;;;UAAiB,qBAAA;EACf,IAAA;EACA,QAAA;IACE,EAAA;IACA,IAAA;IACA,IAAA;EAAA;AAAA;;AA2CJ;;;iBAnCgB,iCAAA,CACd,KAAA,YACC,qBAAA;;;;UAiCc,0BAAA;EACf,IAAA;IACE,IAAA;EAAA;EAEF,OAAA,GAAU,IAAA,EAAM,IAAA,KAAS,OAAA,CAAQ,MAAA;AAAA;AAAA,KAIvB,6BAAA,GAAgC,0BAAA;AAAA,UAK3B,SAAA;EACf,KAAA;EACA,IAAA;EACA,IAAA;EACA,MAAA;EACA,KAAA,GAAQ,KAAA;EACR,QAAA;AAAA;AAAA,KAGU,cAAA,IAAkB,KAAA,EAAO,SAAA;;AATrC;;iBAcgB,+BAAA;EAA4C,KAAA;AAAA,EAAA,CAC1D,UAAA,WAAqB,CAAA,KACpB,CAAA;AAAA,iBAOa,uBAAA,CACd,SAAA,YACC,SAAA,IAAa,qBAAA;AAAA,iBAIA,gBAAA,kCAAA,CACd,KAAA,EAAO,0BAAA,IACP,IAAA,WACC,0BAAA,CAA2B,IAAA,EAAM,MAAA;AAAA,iBAMd,mBAAA,kCAAA,CACpB,cAAA,EAAgB,0BAAA,CAA2B,IAAA,EAAM,MAAA,GACjD,IAAA,EAAM,IAAA,EACN,MAAA,GAAS,cAAA,GACR,OAAA;EACC,OAAA;EAAe,MAAA,EAAQ,MAAA;AAAA;EAAa,OAAA;EAAgB,KAAA,EAAO,KAAA;AAAA;AAAA,iBAwCzC,2BAAA,CACpB,SAAA,EAAW,qBAAA,EACX,KAAA,EAAO,0BAAA,IACP,MAAA,GAAS,cAAA,GACR,OAAA;EAAU,UAAA;EAAgC,KAAA;AAAA;AAAA,iBA2C7B,yBAAA,CAA0B,MAAA;EACxC,UAAA;EACA,KAAA;AAAA;EACI,MAAA;AAAA;AAAA,UAiJW,yCAAA;EACf,MAAA,GAAS,cAAA;EACT,YAAA,GAAe,OAAA;IAAW,MAAA;EAAA,aAA6B,OAAA;EACvD,KAAA,IAAS,GAAA;AAAA;;;;;;;;iBAeK,kCAAA,CACd,UAAA,EAAY,GAAA,UACZ,GAAA;;;;;iBAoBc,kCAAA,CACd,MAAA,EAAQ,MAAA,sCACR,KAAA,EAAO,0BAAA,gBACP,UAAA,EAAY,GAAA,UACZ,OAAA,EAAS,yCAAA"}
@@ -71,11 +71,19 @@ interface FlushPendingHeadlessToolInterruptsOptions {
71
71
  }) => void | Promise<void>;
72
72
  defer?: (run: () => void) => void;
73
73
  }
74
+ /**
75
+ * Coalesce rapid headless-tool flush triggers into one microtask so parallel
76
+ * `input.requested` events observed back-to-back batch into a single resume.
77
+ * Vue/Svelte/Angular watchers run synchronously per event; without this,
78
+ * the first interrupt can be claimed before the second arrives and resume
79
+ * splits into staggered single-tool commands.
80
+ */
81
+ declare function scheduleCoalescedHeadlessToolFlush(handledIds: Set<string>, run: () => void): void;
74
82
  /**
75
83
  * Execute and resume all newly seen headless-tool interrupts from a values
76
84
  * payload. Callers own `handledIds` and should clear it when the thread changes.
77
85
  */
78
86
  declare function flushPendingHeadlessToolInterrupts(values: Record<string, unknown> | null | undefined, tools: HeadlessToolImplementation[] | undefined, handledIds: Set<string>, options: FlushPendingHeadlessToolInterruptsOptions): void;
79
87
  //#endregion
80
- export { AnyHeadlessToolImplementation, FlushPendingHeadlessToolInterruptsOptions, HeadlessToolImplementation, HeadlessToolInterrupt, OnToolCallback, ToolEvent, executeHeadlessTool, filterOutHeadlessToolInterrupts, findHeadlessTool, flushPendingHeadlessToolInterrupts, handleHeadlessToolInterrupt, headlessToolResumeCommand, isHeadlessToolInterrupt, parseHeadlessToolInterruptPayload };
88
+ export { AnyHeadlessToolImplementation, FlushPendingHeadlessToolInterruptsOptions, HeadlessToolImplementation, HeadlessToolInterrupt, OnToolCallback, ToolEvent, executeHeadlessTool, filterOutHeadlessToolInterrupts, findHeadlessTool, flushPendingHeadlessToolInterrupts, handleHeadlessToolInterrupt, headlessToolResumeCommand, isHeadlessToolInterrupt, parseHeadlessToolInterruptPayload, scheduleCoalescedHeadlessToolFlush };
81
89
  //# sourceMappingURL=headless-tools.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"headless-tools.d.ts","names":[],"sources":["../src/headless-tools.ts"],"mappings":";;AAUA;;;;;;;UAAiB,qBAAA;EACf,IAAA;EACA,QAAA;IACE,EAAA;IACA,IAAA;IACA,IAAA;EAAA;AAAA;;AA2CJ;;;iBAnCgB,iCAAA,CACd,KAAA,YACC,qBAAA;;;;UAiCc,0BAAA;EACf,IAAA;IACE,IAAA;EAAA;EAEF,OAAA,GAAU,IAAA,EAAM,IAAA,KAAS,OAAA,CAAQ,MAAA;AAAA;AAAA,KAIvB,6BAAA,GAAgC,0BAAA;AAAA,UAK3B,SAAA;EACf,KAAA;EACA,IAAA;EACA,IAAA;EACA,MAAA;EACA,KAAA,GAAQ,KAAA;EACR,QAAA;AAAA;AAAA,KAGU,cAAA,IAAkB,KAAA,EAAO,SAAA;;AATrC;;iBAcgB,+BAAA;EAA4C,KAAA;AAAA,EAAA,CAC1D,UAAA,WAAqB,CAAA,KACpB,CAAA;AAAA,iBAOa,uBAAA,CACd,SAAA,YACC,SAAA,IAAa,qBAAA;AAAA,iBAIA,gBAAA,kCAAA,CACd,KAAA,EAAO,0BAAA,IACP,IAAA,WACC,0BAAA,CAA2B,IAAA,EAAM,MAAA;AAAA,iBAMd,mBAAA,kCAAA,CACpB,cAAA,EAAgB,0BAAA,CAA2B,IAAA,EAAM,MAAA,GACjD,IAAA,EAAM,IAAA,EACN,MAAA,GAAS,cAAA,GACR,OAAA;EACC,OAAA;EAAe,MAAA,EAAQ,MAAA;AAAA;EAAa,OAAA;EAAgB,KAAA,EAAO,KAAA;AAAA;AAAA,iBAwCzC,2BAAA,CACpB,SAAA,EAAW,qBAAA,EACX,KAAA,EAAO,0BAAA,IACP,MAAA,GAAS,cAAA,GACR,OAAA;EAAU,UAAA;EAAgC,KAAA;AAAA;AAAA,iBA2C7B,yBAAA,CAA0B,MAAA;EACxC,UAAA;EACA,KAAA;AAAA;EACI,MAAA;AAAA;AAAA,UAQW,yCAAA;EACf,MAAA,GAAS,cAAA;EACT,YAAA,GAAe,OAAA;IAAW,MAAA;EAAA,aAA6B,OAAA;EACvD,KAAA,IAAS,GAAA;AAAA;;;;;iBAOK,kCAAA,CACd,MAAA,EAAQ,MAAA,sCACR,KAAA,EAAO,0BAAA,gBACP,UAAA,EAAY,GAAA,UACZ,OAAA,EAAS,yCAAA"}
1
+ {"version":3,"file":"headless-tools.d.ts","names":[],"sources":["../src/headless-tools.ts"],"mappings":";;AAUA;;;;;;;UAAiB,qBAAA;EACf,IAAA;EACA,QAAA;IACE,EAAA;IACA,IAAA;IACA,IAAA;EAAA;AAAA;;AA2CJ;;;iBAnCgB,iCAAA,CACd,KAAA,YACC,qBAAA;;;;UAiCc,0BAAA;EACf,IAAA;IACE,IAAA;EAAA;EAEF,OAAA,GAAU,IAAA,EAAM,IAAA,KAAS,OAAA,CAAQ,MAAA;AAAA;AAAA,KAIvB,6BAAA,GAAgC,0BAAA;AAAA,UAK3B,SAAA;EACf,KAAA;EACA,IAAA;EACA,IAAA;EACA,MAAA;EACA,KAAA,GAAQ,KAAA;EACR,QAAA;AAAA;AAAA,KAGU,cAAA,IAAkB,KAAA,EAAO,SAAA;;AATrC;;iBAcgB,+BAAA;EAA4C,KAAA;AAAA,EAAA,CAC1D,UAAA,WAAqB,CAAA,KACpB,CAAA;AAAA,iBAOa,uBAAA,CACd,SAAA,YACC,SAAA,IAAa,qBAAA;AAAA,iBAIA,gBAAA,kCAAA,CACd,KAAA,EAAO,0BAAA,IACP,IAAA,WACC,0BAAA,CAA2B,IAAA,EAAM,MAAA;AAAA,iBAMd,mBAAA,kCAAA,CACpB,cAAA,EAAgB,0BAAA,CAA2B,IAAA,EAAM,MAAA,GACjD,IAAA,EAAM,IAAA,EACN,MAAA,GAAS,cAAA,GACR,OAAA;EACC,OAAA;EAAe,MAAA,EAAQ,MAAA;AAAA;EAAa,OAAA;EAAgB,KAAA,EAAO,KAAA;AAAA;AAAA,iBAwCzC,2BAAA,CACpB,SAAA,EAAW,qBAAA,EACX,KAAA,EAAO,0BAAA,IACP,MAAA,GAAS,cAAA,GACR,OAAA;EAAU,UAAA;EAAgC,KAAA;AAAA;AAAA,iBA2C7B,yBAAA,CAA0B,MAAA;EACxC,UAAA;EACA,KAAA;AAAA;EACI,MAAA;AAAA;AAAA,UAiJW,yCAAA;EACf,MAAA,GAAS,cAAA;EACT,YAAA,GAAe,OAAA;IAAW,MAAA;EAAA,aAA6B,OAAA;EACvD,KAAA,IAAS,GAAA;AAAA;;;;;;;;iBAeK,kCAAA,CACd,UAAA,EAAY,GAAA,UACZ,GAAA;;;;;iBAoBc,kCAAA,CACd,MAAA,EAAQ,MAAA,sCACR,KAAA,EAAO,0BAAA,gBACP,UAAA,EAAY,GAAA,UACZ,OAAA,EAAS,yCAAA"}
@@ -100,6 +100,109 @@ function headlessToolResumeCommand(result) {
100
100
  return { resume: result.toolCallId ? { [result.toolCallId]: result.value } : result.value };
101
101
  }
102
102
  /**
103
+ * Merge headless-tool results into one resume command. Use interrupt-id keys
104
+ * whenever the stream provided them so the resume does not need to rediscover
105
+ * a pending interrupt from mutable client state.
106
+ */
107
+ function headlessToolsBatchResumeCommand(entries) {
108
+ if (entries.length === 0) return { resume: {} };
109
+ if (!entries.every((entry) => entry.interruptId.length > 0) && entries.length === 1) {
110
+ const [entry] = entries;
111
+ return headlessToolResumeCommand({
112
+ toolCallId: entry.toolCallId,
113
+ value: entry.value
114
+ });
115
+ }
116
+ const resume = {};
117
+ for (const entry of entries) {
118
+ if (entry.interruptId.length === 0) continue;
119
+ resume[entry.interruptId] = entry.toolCallId != null && entry.toolCallId.length > 0 ? { [entry.toolCallId]: entry.value } : entry.value;
120
+ }
121
+ return { resume };
122
+ }
123
+ /**
124
+ * True when every top-level resume key is a graph task interrupt id
125
+ * (32-char hex from `values.__interrupt__`).
126
+ */
127
+ function isInterruptIdKeyedResume(resume) {
128
+ if (resume == null || typeof resume !== "object" || Array.isArray(resume)) return false;
129
+ const keys = Object.keys(resume);
130
+ if (keys.length === 0) return false;
131
+ return keys.every((key) => /^[0-9a-f]{32}$/i.test(key));
132
+ }
133
+ /**
134
+ * Normalize `command.resume` into the `run.start` input the API turns
135
+ * into `Command({ resume })`. Interrupt-id keyed payloads pass through;
136
+ * tool-call-keyed and generic payloads are wrapped under the matching
137
+ * protocol interrupt id.
138
+ */
139
+ function buildResumeRunInput(resume, interrupts, resolvedInterruptIds) {
140
+ if (resume == null) return null;
141
+ if (isInterruptIdKeyedResume(resume)) return resume;
142
+ const target = resolveInterruptTargetForHeadlessResume(resume, interrupts, resolvedInterruptIds);
143
+ if (target == null) return null;
144
+ return { [target.interruptId]: resume };
145
+ }
146
+ /**
147
+ * Reads the tool-call id from a headless-tool resume command shaped as
148
+ * `{ [toolCallId]: result }`.
149
+ */
150
+ function extractHeadlessToolCallIdFromResumeCommand(resume) {
151
+ if (resume == null || typeof resume !== "object" || Array.isArray(resume)) return;
152
+ const keys = Object.keys(resume);
153
+ if (keys.length !== 1) return void 0;
154
+ return keys[0];
155
+ }
156
+ /**
157
+ * Pick the protocol interrupt that matches a headless-tool resume payload.
158
+ * Falls back to the newest unresolved interrupt for non-keyed resumes.
159
+ */
160
+ function resolveInterruptTargetForHeadlessResume(resume, interrupts, resolvedInterruptIds) {
161
+ const toolCallId = extractHeadlessToolCallIdFromResumeCommand(resume);
162
+ if (toolCallId != null) for (let i = interrupts.length - 1; i >= 0; i -= 1) {
163
+ const entry = interrupts[i];
164
+ if (entry == null || resolvedInterruptIds.has(entry.interruptId)) continue;
165
+ if (parseHeadlessToolInterruptPayload(entry.payload)?.toolCall.id === toolCallId) return {
166
+ interruptId: entry.interruptId,
167
+ namespace: [...entry.namespace]
168
+ };
169
+ }
170
+ for (let i = interrupts.length - 1; i >= 0; i -= 1) {
171
+ const entry = interrupts[i];
172
+ if (entry == null || resolvedInterruptIds.has(entry.interruptId)) continue;
173
+ return {
174
+ interruptId: entry.interruptId,
175
+ namespace: [...entry.namespace]
176
+ };
177
+ }
178
+ return null;
179
+ }
180
+ const coalescedHeadlessFlushes = /* @__PURE__ */ new WeakMap();
181
+ /**
182
+ * Coalesce rapid headless-tool flush triggers into one microtask so parallel
183
+ * `input.requested` events observed back-to-back batch into a single resume.
184
+ * Vue/Svelte/Angular watchers run synchronously per event; without this,
185
+ * the first interrupt can be claimed before the second arrives and resume
186
+ * splits into staggered single-tool commands.
187
+ */
188
+ function scheduleCoalescedHeadlessToolFlush(handledIds, run) {
189
+ let state = coalescedHeadlessFlushes.get(handledIds);
190
+ if (state == null) {
191
+ state = {
192
+ scheduled: false,
193
+ run: () => {}
194
+ };
195
+ coalescedHeadlessFlushes.set(handledIds, state);
196
+ }
197
+ state.run = run;
198
+ if (state.scheduled) return;
199
+ state.scheduled = true;
200
+ Promise.resolve().then(() => {
201
+ state.scheduled = false;
202
+ state.run();
203
+ });
204
+ }
205
+ /**
103
206
  * Execute and resume all newly seen headless-tool interrupts from a values
104
207
  * payload. Callers own `handledIds` and should clear it when the thread changes.
105
208
  */
@@ -108,20 +211,41 @@ function flushPendingHeadlessToolInterrupts(values, tools, handledIds, options)
108
211
  const interrupts = values.__interrupt__;
109
212
  if (!Array.isArray(interrupts) || interrupts.length === 0) return;
110
213
  const defer = options.defer ?? ((run) => run());
214
+ const pending = [];
215
+ const seenToolCallIds = /* @__PURE__ */ new Set();
111
216
  for (const interrupt of interrupts) {
112
217
  const headlessInterrupt = parseHeadlessToolInterruptPayload(interrupt.value);
113
218
  if (!headlessInterrupt) continue;
114
219
  const interruptId = interrupt.id ?? headlessInterrupt.toolCall.id ?? "";
220
+ const toolCallId = headlessInterrupt.toolCall.id ?? "";
115
221
  if (handledIds.has(interruptId)) continue;
222
+ if (toolCallId && handledIds.has(toolCallId)) continue;
223
+ if (toolCallId && seenToolCallIds.has(toolCallId)) continue;
224
+ if (toolCallId) seenToolCallIds.add(toolCallId);
116
225
  handledIds.add(interruptId);
117
- defer(() => {
118
- handleHeadlessToolInterrupt(headlessInterrupt, tools, options.onTool).then((result) => {
119
- options.resumeSubmit(headlessToolResumeCommand(result));
120
- });
226
+ if (toolCallId) handledIds.add(toolCallId);
227
+ pending.push({
228
+ interruptId,
229
+ headlessInterrupt,
230
+ toolCallId
121
231
  });
122
232
  }
233
+ if (pending.length === 0) return;
234
+ defer(() => {
235
+ (async () => {
236
+ const results = await Promise.all(pending.map(async ({ interruptId, headlessInterrupt, toolCallId }) => {
237
+ const result = await handleHeadlessToolInterrupt(headlessInterrupt, tools, options.onTool);
238
+ return {
239
+ interruptId,
240
+ toolCallId: result.toolCallId ?? toolCallId,
241
+ value: result.value
242
+ };
243
+ }));
244
+ await Promise.resolve(options.resumeSubmit(headlessToolsBatchResumeCommand(results)));
245
+ })();
246
+ });
123
247
  }
124
248
  //#endregion
125
- export { executeHeadlessTool, filterOutHeadlessToolInterrupts, findHeadlessTool, flushPendingHeadlessToolInterrupts, handleHeadlessToolInterrupt, headlessToolResumeCommand, isHeadlessToolInterrupt, parseHeadlessToolInterruptPayload };
249
+ export { buildResumeRunInput, executeHeadlessTool, filterOutHeadlessToolInterrupts, findHeadlessTool, flushPendingHeadlessToolInterrupts, handleHeadlessToolInterrupt, headlessToolResumeCommand, isHeadlessToolInterrupt, parseHeadlessToolInterruptPayload, resolveInterruptTargetForHeadlessResume, scheduleCoalescedHeadlessToolFlush };
126
250
 
127
251
  //# sourceMappingURL=headless-tools.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"headless-tools.js","names":[],"sources":["../src/headless-tools.ts"],"sourcesContent":["import type { Interrupt } from \"./schema.js\";\n\n/**\n * Represents a headless tool interrupt payload emitted by LangChain's\n * schema-only `tool({ ... })` overload.\n *\n * Servers may serialize the nested tool call as `toolCall` (JS) or\n * `tool_call` (Python). Use {@link parseHeadlessToolInterruptPayload} to\n * normalize either shape before reading fields.\n */\nexport interface HeadlessToolInterrupt {\n type: \"tool\";\n toolCall: {\n id: string | undefined;\n name: string;\n args: unknown;\n };\n}\n\n/**\n * Parses a headless-tool interrupt `value` from the graph. Accepts both\n * `toolCall` (LangChain JS) and `tool_call` (Python / JSON snake_case).\n */\nexport function parseHeadlessToolInterruptPayload(\n value: unknown\n): HeadlessToolInterrupt | null {\n if (typeof value !== \"object\" || value == null) {\n return null;\n }\n const v = value as Record<string, unknown>;\n if (v.type !== \"tool\") {\n return null;\n }\n\n const rawTc = v.toolCall ?? v.tool_call;\n if (typeof rawTc !== \"object\" || rawTc == null) {\n return null;\n }\n const tc = rawTc as Record<string, unknown>;\n if (typeof tc.name !== \"string\") {\n return null;\n }\n\n const id = typeof tc.id === \"string\" ? tc.id : undefined;\n\n return {\n type: \"tool\",\n toolCall: {\n id,\n name: tc.name,\n args: tc.args,\n },\n };\n}\n\n/**\n * Client-side implementation returned by `headlessTool.implement(...)`.\n */\nexport interface HeadlessToolImplementation<Args = unknown, Output = unknown> {\n tool: {\n name: string;\n };\n execute: (args: Args) => Promise<Output>;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyHeadlessToolImplementation = HeadlessToolImplementation<\n any,\n any\n>;\n\nexport interface ToolEvent {\n phase: \"start\" | \"success\" | \"error\";\n name: string;\n args: unknown;\n result?: unknown;\n error?: Error;\n duration?: number;\n}\n\nexport type OnToolCallback = (event: ToolEvent) => void;\n\n/**\n * Strip headless-tool interrupts from a user-facing interrupt list.\n */\nexport function filterOutHeadlessToolInterrupts<T extends { value?: unknown }>(\n interrupts: readonly T[]\n): T[] {\n return interrupts.filter(\n (interrupt) =>\n interrupt.value == null || !isHeadlessToolInterrupt(interrupt.value)\n );\n}\n\nexport function isHeadlessToolInterrupt(\n interrupt: unknown\n): interrupt is HeadlessToolInterrupt {\n return parseHeadlessToolInterruptPayload(interrupt) != null;\n}\n\nexport function findHeadlessTool<Args = unknown, Output = unknown>(\n tools: HeadlessToolImplementation[],\n name: string\n): HeadlessToolImplementation<Args, Output> | undefined {\n return tools.find((tool) => tool.tool.name === name) as\n | HeadlessToolImplementation<Args, Output>\n | undefined;\n}\n\nexport async function executeHeadlessTool<Args = unknown, Output = unknown>(\n implementation: HeadlessToolImplementation<Args, Output>,\n args: Args,\n onTool?: OnToolCallback\n): Promise<\n { success: true; result: Output } | { success: false; error: Error }\n> {\n const startTime = Date.now();\n\n onTool?.({\n phase: \"start\",\n name: implementation.tool.name,\n args,\n });\n\n try {\n const result = await implementation.execute(args);\n const duration = Date.now() - startTime;\n\n onTool?.({\n phase: \"success\",\n name: implementation.tool.name,\n args,\n result,\n duration,\n });\n\n return { success: true, result };\n } catch (err) {\n // oxlint-disable-next-line no-instanceof/no-instanceof\n const error = err instanceof Error ? err : new Error(String(err));\n const duration = Date.now() - startTime;\n\n onTool?.({\n phase: \"error\",\n name: implementation.tool.name,\n args,\n error,\n duration,\n });\n\n return { success: false, error };\n }\n}\n\nexport async function handleHeadlessToolInterrupt(\n interrupt: HeadlessToolInterrupt,\n tools: HeadlessToolImplementation[],\n onTool?: OnToolCallback\n): Promise<{ toolCallId: string | undefined; value: unknown }> {\n const { toolCall } = interrupt;\n const implementation = findHeadlessTool(tools, toolCall.name);\n\n if (!implementation) {\n const error = new Error(\n `Headless tool \"${toolCall.name}\" is not registered. ` +\n `Available tools: ${tools.map((tool) => tool.tool.name).join(\", \") || \"none\"}`\n );\n\n onTool?.({\n phase: \"error\",\n name: toolCall.name,\n args: toolCall.args,\n error,\n duration: 0,\n });\n\n return {\n toolCallId: toolCall.id,\n value: { error: error.message },\n };\n }\n\n const result = await executeHeadlessTool(\n implementation,\n toolCall.args as never,\n onTool\n );\n\n if (result.success) {\n return {\n toolCallId: toolCall.id,\n value: result.result,\n };\n }\n\n return {\n toolCallId: toolCall.id,\n value: { error: result.error.message },\n };\n}\n\nexport function headlessToolResumeCommand(result: {\n toolCallId: string | undefined;\n value: unknown;\n}): { resume: unknown } {\n return {\n resume: result.toolCallId\n ? { [result.toolCallId]: result.value }\n : result.value,\n };\n}\n\nexport interface FlushPendingHeadlessToolInterruptsOptions {\n onTool?: OnToolCallback;\n resumeSubmit: (command: { resume: unknown }) => void | Promise<void>;\n defer?: (run: () => void) => void;\n}\n\n/**\n * Execute and resume all newly seen headless-tool interrupts from a values\n * payload. Callers own `handledIds` and should clear it when the thread changes.\n */\nexport function flushPendingHeadlessToolInterrupts(\n values: Record<string, unknown> | null | undefined,\n tools: HeadlessToolImplementation[] | undefined,\n handledIds: Set<string>,\n options: FlushPendingHeadlessToolInterruptsOptions\n): void {\n if (!tools?.length || !values) return;\n\n const interrupts = values.__interrupt__;\n if (!Array.isArray(interrupts) || interrupts.length === 0) return;\n\n const defer = options.defer ?? ((run) => run());\n\n for (const interrupt of interrupts as Interrupt[]) {\n const headlessInterrupt = parseHeadlessToolInterruptPayload(\n interrupt.value\n );\n if (!headlessInterrupt) continue;\n\n const interruptId = interrupt.id ?? headlessInterrupt.toolCall.id ?? \"\";\n if (handledIds.has(interruptId)) continue;\n handledIds.add(interruptId);\n\n defer(() => {\n void handleHeadlessToolInterrupt(\n headlessInterrupt,\n tools,\n options.onTool\n ).then((result) => {\n void options.resumeSubmit(headlessToolResumeCommand(result));\n });\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"headless-tools.js","names":[],"sources":["../src/headless-tools.ts"],"sourcesContent":["import type { Interrupt } from \"./schema.js\";\n\n/**\n * Represents a headless tool interrupt payload emitted by LangChain's\n * schema-only `tool({ ... })` overload.\n *\n * Servers may serialize the nested tool call as `toolCall` (JS) or\n * `tool_call` (Python). Use {@link parseHeadlessToolInterruptPayload} to\n * normalize either shape before reading fields.\n */\nexport interface HeadlessToolInterrupt {\n type: \"tool\";\n toolCall: {\n id: string | undefined;\n name: string;\n args: unknown;\n };\n}\n\n/**\n * Parses a headless-tool interrupt `value` from the graph. Accepts both\n * `toolCall` (LangChain JS) and `tool_call` (Python / JSON snake_case).\n */\nexport function parseHeadlessToolInterruptPayload(\n value: unknown\n): HeadlessToolInterrupt | null {\n if (typeof value !== \"object\" || value == null) {\n return null;\n }\n const v = value as Record<string, unknown>;\n if (v.type !== \"tool\") {\n return null;\n }\n\n const rawTc = v.toolCall ?? v.tool_call;\n if (typeof rawTc !== \"object\" || rawTc == null) {\n return null;\n }\n const tc = rawTc as Record<string, unknown>;\n if (typeof tc.name !== \"string\") {\n return null;\n }\n\n const id = typeof tc.id === \"string\" ? tc.id : undefined;\n\n return {\n type: \"tool\",\n toolCall: {\n id,\n name: tc.name,\n args: tc.args,\n },\n };\n}\n\n/**\n * Client-side implementation returned by `headlessTool.implement(...)`.\n */\nexport interface HeadlessToolImplementation<Args = unknown, Output = unknown> {\n tool: {\n name: string;\n };\n execute: (args: Args) => Promise<Output>;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyHeadlessToolImplementation = HeadlessToolImplementation<\n any,\n any\n>;\n\nexport interface ToolEvent {\n phase: \"start\" | \"success\" | \"error\";\n name: string;\n args: unknown;\n result?: unknown;\n error?: Error;\n duration?: number;\n}\n\nexport type OnToolCallback = (event: ToolEvent) => void;\n\n/**\n * Strip headless-tool interrupts from a user-facing interrupt list.\n */\nexport function filterOutHeadlessToolInterrupts<T extends { value?: unknown }>(\n interrupts: readonly T[]\n): T[] {\n return interrupts.filter(\n (interrupt) =>\n interrupt.value == null || !isHeadlessToolInterrupt(interrupt.value)\n );\n}\n\nexport function isHeadlessToolInterrupt(\n interrupt: unknown\n): interrupt is HeadlessToolInterrupt {\n return parseHeadlessToolInterruptPayload(interrupt) != null;\n}\n\nexport function findHeadlessTool<Args = unknown, Output = unknown>(\n tools: HeadlessToolImplementation[],\n name: string\n): HeadlessToolImplementation<Args, Output> | undefined {\n return tools.find((tool) => tool.tool.name === name) as\n | HeadlessToolImplementation<Args, Output>\n | undefined;\n}\n\nexport async function executeHeadlessTool<Args = unknown, Output = unknown>(\n implementation: HeadlessToolImplementation<Args, Output>,\n args: Args,\n onTool?: OnToolCallback\n): Promise<\n { success: true; result: Output } | { success: false; error: Error }\n> {\n const startTime = Date.now();\n\n onTool?.({\n phase: \"start\",\n name: implementation.tool.name,\n args,\n });\n\n try {\n const result = await implementation.execute(args);\n const duration = Date.now() - startTime;\n\n onTool?.({\n phase: \"success\",\n name: implementation.tool.name,\n args,\n result,\n duration,\n });\n\n return { success: true, result };\n } catch (err) {\n // oxlint-disable-next-line no-instanceof/no-instanceof\n const error = err instanceof Error ? err : new Error(String(err));\n const duration = Date.now() - startTime;\n\n onTool?.({\n phase: \"error\",\n name: implementation.tool.name,\n args,\n error,\n duration,\n });\n\n return { success: false, error };\n }\n}\n\nexport async function handleHeadlessToolInterrupt(\n interrupt: HeadlessToolInterrupt,\n tools: HeadlessToolImplementation[],\n onTool?: OnToolCallback\n): Promise<{ toolCallId: string | undefined; value: unknown }> {\n const { toolCall } = interrupt;\n const implementation = findHeadlessTool(tools, toolCall.name);\n\n if (!implementation) {\n const error = new Error(\n `Headless tool \"${toolCall.name}\" is not registered. ` +\n `Available tools: ${tools.map((tool) => tool.tool.name).join(\", \") || \"none\"}`\n );\n\n onTool?.({\n phase: \"error\",\n name: toolCall.name,\n args: toolCall.args,\n error,\n duration: 0,\n });\n\n return {\n toolCallId: toolCall.id,\n value: { error: error.message },\n };\n }\n\n const result = await executeHeadlessTool(\n implementation,\n toolCall.args as never,\n onTool\n );\n\n if (result.success) {\n return {\n toolCallId: toolCall.id,\n value: result.result,\n };\n }\n\n return {\n toolCallId: toolCall.id,\n value: { error: result.error.message },\n };\n}\n\nexport function headlessToolResumeCommand(result: {\n toolCallId: string | undefined;\n value: unknown;\n}): { resume: unknown } {\n return {\n resume: result.toolCallId\n ? { [result.toolCallId]: result.value }\n : result.value,\n };\n}\n\n/**\n * Merge headless-tool results into one resume command. Use interrupt-id keys\n * whenever the stream provided them so the resume does not need to rediscover\n * a pending interrupt from mutable client state.\n */\nexport function headlessToolsBatchResumeCommand(\n entries: ReadonlyArray<{\n interruptId: string;\n toolCallId: string | undefined;\n value: unknown;\n }>\n): { resume: unknown } {\n if (entries.length === 0) {\n return { resume: {} };\n }\n\n const hasInterruptIds = entries.every(\n (entry) => entry.interruptId.length > 0\n );\n if (!hasInterruptIds && entries.length === 1) {\n const [entry] = entries;\n return headlessToolResumeCommand({\n toolCallId: entry.toolCallId,\n value: entry.value,\n });\n }\n\n const resume: Record<string, unknown> = {};\n for (const entry of entries) {\n if (entry.interruptId.length === 0) continue;\n resume[entry.interruptId] =\n entry.toolCallId != null && entry.toolCallId.length > 0\n ? { [entry.toolCallId]: entry.value }\n : entry.value;\n }\n return { resume };\n}\n\n/**\n * True when every top-level resume key is a graph task interrupt id\n * (32-char hex from `values.__interrupt__`).\n */\nexport function isInterruptIdKeyedResume(resume: unknown): boolean {\n if (resume == null || typeof resume !== \"object\" || Array.isArray(resume)) {\n return false;\n }\n const keys = Object.keys(resume as Record<string, unknown>);\n if (keys.length === 0) return false;\n return keys.every((key) => /^[0-9a-f]{32}$/i.test(key));\n}\n\n/**\n * Normalize `command.resume` into the `run.start` input the API turns\n * into `Command({ resume })`. Interrupt-id keyed payloads pass through;\n * tool-call-keyed and generic payloads are wrapped under the matching\n * protocol interrupt id.\n */\nexport function buildResumeRunInput(\n resume: unknown,\n interrupts: readonly ProtocolInterruptEntry[],\n resolvedInterruptIds: ReadonlySet<string>\n): Record<string, unknown> | null {\n if (resume == null) return null;\n if (isInterruptIdKeyedResume(resume)) {\n return resume as Record<string, unknown>;\n }\n\n const target = resolveInterruptTargetForHeadlessResume(\n resume,\n interrupts,\n resolvedInterruptIds\n );\n if (target == null) return null;\n\n return { [target.interruptId]: resume };\n}\n\n/**\n * Reads the tool-call id from a headless-tool resume command shaped as\n * `{ [toolCallId]: result }`.\n */\nexport function extractHeadlessToolCallIdFromResumeCommand(\n resume: unknown\n): string | undefined {\n if (resume == null || typeof resume !== \"object\" || Array.isArray(resume)) {\n return undefined;\n }\n const keys = Object.keys(resume as Record<string, unknown>);\n if (keys.length !== 1) return undefined;\n return keys[0];\n}\n\nexport interface ProtocolInterruptEntry {\n interruptId: string;\n namespace: string[];\n payload: unknown;\n}\n\n/**\n * Pick the protocol interrupt that matches a headless-tool resume payload.\n * Falls back to the newest unresolved interrupt for non-keyed resumes.\n */\nexport function resolveInterruptTargetForHeadlessResume(\n resume: unknown,\n interrupts: readonly ProtocolInterruptEntry[],\n resolvedInterruptIds: ReadonlySet<string>\n): { interruptId: string; namespace: string[] } | null {\n const toolCallId = extractHeadlessToolCallIdFromResumeCommand(resume);\n if (toolCallId != null) {\n for (let i = interrupts.length - 1; i >= 0; i -= 1) {\n const entry = interrupts[i];\n if (entry == null || resolvedInterruptIds.has(entry.interruptId)) {\n continue;\n }\n const headless = parseHeadlessToolInterruptPayload(entry.payload);\n if (headless?.toolCall.id === toolCallId) {\n return {\n interruptId: entry.interruptId,\n namespace: [...entry.namespace],\n };\n }\n }\n }\n\n for (let i = interrupts.length - 1; i >= 0; i -= 1) {\n const entry = interrupts[i];\n if (entry == null || resolvedInterruptIds.has(entry.interruptId)) {\n continue;\n }\n return {\n interruptId: entry.interruptId,\n namespace: [...entry.namespace],\n };\n }\n return null;\n}\n\nexport interface FlushPendingHeadlessToolInterruptsOptions {\n onTool?: OnToolCallback;\n resumeSubmit: (command: { resume: unknown }) => void | Promise<void>;\n defer?: (run: () => void) => void;\n}\n\nconst coalescedHeadlessFlushes = new WeakMap<\n Set<string>,\n { scheduled: boolean; run: () => void }\n>();\n\n/**\n * Coalesce rapid headless-tool flush triggers into one microtask so parallel\n * `input.requested` events observed back-to-back batch into a single resume.\n * Vue/Svelte/Angular watchers run synchronously per event; without this,\n * the first interrupt can be claimed before the second arrives and resume\n * splits into staggered single-tool commands.\n */\nexport function scheduleCoalescedHeadlessToolFlush(\n handledIds: Set<string>,\n run: () => void\n): void {\n let state = coalescedHeadlessFlushes.get(handledIds);\n if (state == null) {\n state = { scheduled: false, run: () => {} };\n coalescedHeadlessFlushes.set(handledIds, state);\n }\n state.run = run;\n if (state.scheduled) return;\n state.scheduled = true;\n void Promise.resolve().then(() => {\n state!.scheduled = false;\n state!.run();\n });\n}\n\n/**\n * Execute and resume all newly seen headless-tool interrupts from a values\n * payload. Callers own `handledIds` and should clear it when the thread changes.\n */\nexport function flushPendingHeadlessToolInterrupts(\n values: Record<string, unknown> | null | undefined,\n tools: HeadlessToolImplementation[] | undefined,\n handledIds: Set<string>,\n options: FlushPendingHeadlessToolInterruptsOptions\n): void {\n if (!tools?.length || !values) return;\n\n const interrupts = values.__interrupt__;\n if (!Array.isArray(interrupts) || interrupts.length === 0) return;\n\n const defer = options.defer ?? ((run) => run());\n const pending: Array<{\n interruptId: string;\n headlessInterrupt: HeadlessToolInterrupt;\n toolCallId: string;\n }> = [];\n const seenToolCallIds = new Set<string>();\n\n for (const interrupt of interrupts as Interrupt[]) {\n const headlessInterrupt = parseHeadlessToolInterruptPayload(\n interrupt.value\n );\n if (!headlessInterrupt) continue;\n\n const interruptId = interrupt.id ?? headlessInterrupt.toolCall.id ?? \"\";\n const toolCallId = headlessInterrupt.toolCall.id ?? \"\";\n if (handledIds.has(interruptId)) continue;\n // v2 protocol runs mirror the same headless-tool interrupt in both\n // `values.__interrupt__` and `rootStore.interrupts` with different\n // ids (graph/task id vs protocol interrupt_id). The headless-tool\n // effect can also re-run after the first resume clears\n // `rootStore.interrupts` while `values.__interrupt__` is still\n // present — persist tool call ids in the caller-owned set so we\n // only execute + resume once per pending tool call.\n if (toolCallId && handledIds.has(toolCallId)) continue;\n if (toolCallId && seenToolCallIds.has(toolCallId)) continue;\n if (toolCallId) seenToolCallIds.add(toolCallId);\n\n // Claim before defer so a second flush in the same tick cannot\n // schedule a duplicate execute/resume for the same interrupt.\n handledIds.add(interruptId);\n if (toolCallId) handledIds.add(toolCallId);\n\n pending.push({ interruptId, headlessInterrupt, toolCallId });\n }\n\n if (pending.length === 0) return;\n\n defer(() => {\n void (async () => {\n const results = await Promise.all(\n pending.map(async ({ interruptId, headlessInterrupt, toolCallId }) => {\n const result = await handleHeadlessToolInterrupt(\n headlessInterrupt,\n tools,\n options.onTool\n );\n return {\n interruptId,\n toolCallId: result.toolCallId ?? toolCallId,\n value: result.value,\n };\n })\n );\n await Promise.resolve(\n options.resumeSubmit(headlessToolsBatchResumeCommand(results))\n );\n })();\n });\n}\n"],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -36,3 +36,4 @@ exports.headlessToolResumeCommand = require_headless_tools.headlessToolResumeCom
36
36
  exports.isHeadlessToolInterrupt = require_headless_tools.isHeadlessToolInterrupt;
37
37
  exports.overrideFetchImplementation = require_fetch.overrideFetchImplementation;
38
38
  exports.parseHeadlessToolInterruptPayload = require_headless_tools.parseHeadlessToolInterruptPayload;
39
+ exports.scheduleCoalescedHeadlessToolFlush = require_headless_tools.scheduleCoalescedHeadlessToolFlush;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Assistant, AssistantBase, AssistantGraph, AssistantVersion, AssistantsSearchResponse, Checkpoint, Config, Cron, CronCreateForThreadResponse, CronCreateResponse, DefaultValues, GraphSchema, Interrupt, Item, ListNamespaceResponse, Metadata, Run, SearchItem, SearchItemsResponse, Thread, ThreadState, ThreadStatus, ThreadTask } from "./schema.cjs";
2
- import { AIMessage, DefaultToolCall, FunctionMessage, HumanMessage, Message, RemoveMessage, SystemMessage, ToolCallFromTool, ToolCallState, ToolCallWithResult, ToolCallsFromTools, ToolMessage } from "./types.messages.cjs";
2
+ import { AIMessage, DefaultToolCall, FunctionMessage, HumanMessage, InferToolOutput, Message, RemoveMessage, SystemMessage, ToolCallFromTool, ToolCallState, ToolCallWithResult, ToolCallsFromTools, ToolMessage } from "./types.messages.cjs";
3
3
  import { CustomStreamEvent, DebugStreamEvent, ErrorStreamEvent, EventsStreamEvent, FeedbackStreamEvent, MessagesStreamEvent, MessagesTupleStreamEvent, MetadataStreamEvent, StreamMode, ToolProgress, ToolsStreamEvent, UpdatesStreamEvent, ValuesStreamEvent } from "./types.stream.cjs";
4
4
  import { Command, OnConflictBehavior, RunsInvokePayload, StreamEvent } from "./types.cjs";
5
5
  import { ClientConfig, RequestHook, getApiKey } from "./client/base.cjs";
@@ -16,11 +16,11 @@ import { HttpAgentServerAdapter, HttpAgentServerAdapterOptions } from "./client/
16
16
  import { Client } from "./client/index.cjs";
17
17
  import { overrideFetchImplementation } from "./singletons/fetch.cjs";
18
18
  import { BagTemplate } from "./types.template.cjs";
19
- import { AnyHeadlessToolImplementation, FlushPendingHeadlessToolInterruptsOptions, HeadlessToolImplementation, HeadlessToolInterrupt, OnToolCallback, ToolEvent, executeHeadlessTool, filterOutHeadlessToolInterrupts, findHeadlessTool, flushPendingHeadlessToolInterrupts, handleHeadlessToolInterrupt, headlessToolResumeCommand, isHeadlessToolInterrupt, parseHeadlessToolInterruptPayload } from "./headless-tools.cjs";
19
+ import { AnyHeadlessToolImplementation, FlushPendingHeadlessToolInterruptsOptions, HeadlessToolImplementation, HeadlessToolInterrupt, OnToolCallback, ToolEvent, executeHeadlessTool, filterOutHeadlessToolInterrupts, findHeadlessTool, flushPendingHeadlessToolInterrupts, handleHeadlessToolInterrupt, headlessToolResumeCommand, isHeadlessToolInterrupt, parseHeadlessToolInterruptPayload, scheduleCoalescedHeadlessToolFlush } from "./headless-tools.cjs";
20
20
  import { BaseStream, StateRecord } from "./ui/stream/base.cjs";
21
21
  import { UseAgentStream, UseAgentStreamOptions } from "./ui/stream/agent.cjs";
22
22
  import { UseDeepAgentStream, UseDeepAgentStreamOptions } from "./ui/stream/deep-agent.cjs";
23
23
  import { InferBag, InferNodeNames, InferNodeReturnTypes, InferStateType, InferSubagentStates, InferToolCalls, ResolveStreamInterface, ResolveStreamOptions } from "./ui/stream/index.cjs";
24
24
  import { NAMESPACE_SEPARATOR } from "./stream/constants.cjs";
25
25
  import { StreamError } from "./ui/errors.cjs";
26
- export { type AIMessage, type AgentServerAdapter, type AnyHeadlessToolImplementation, type AnyMediaHandle, type AssembledMessage, type Assistant, type AssistantBase, type AssistantGraph, type AssistantVersion, type AssistantsSearchResponse, type AudioMedia, type BagTemplate, BaseStream, type Checkpoint, Client, type ClientConfig, type Command, type Config, type Cron, type CronCreateForThreadResponse, type CronCreateResponse, type CustomStreamEvent, type DebugStreamEvent, type DefaultToolCall, type DefaultValues, type ErrorStreamEvent, type EventSubscription, type EventsStreamEvent, type FeedbackStreamEvent, type FileMedia, type FlushPendingHeadlessToolInterruptsOptions, type FunctionMessage, type GraphSchema, type HeadlessToolImplementation, type HeadlessToolInterrupt, HttpAgentServerAdapter, type HttpAgentServerAdapterOptions, type HumanMessage, type ImageMedia, InferBag, InferNodeNames, InferNodeReturnTypes, InferStateType, InferSubagentStates, InferToolCalls, type Interrupt, type Item, type ListNamespaceResponse, MediaAssembler, type MediaAssemblerCallbacks, type MediaAssemblerOptions, MediaAssemblyError, type MediaAssemblyErrorKind, type MediaBase, type MediaBlockType, type Message, MessageAssembler, type MessageSubscription, type MessagesStreamEvent, type MessagesTupleStreamEvent, type Metadata, type MetadataStreamEvent, NAMESPACE_SEPARATOR, type OnConflictBehavior, type OnToolCallback, ProtocolError, type ProtocolRequestHook, ProtocolSseTransportAdapter, type ProtocolSseTransportOptions, type ProtocolTransportPaths, ProtocolWebSocketTransportAdapter, type ProtocolWebSocketTransportOptions, type RemoveMessage, type RequestHook, ResolveStreamInterface, ResolveStreamOptions, type Run, type RunsInvokePayload, type SearchItem, type SearchItemsResponse, type SessionOrderingState, StateRecord, StreamError, type StreamEvent, type StreamMode, SubscriptionHandle, type SystemMessage, type Thread, type ThreadExtension, type ThreadExtensions, type ThreadState, type ThreadStatus, ThreadStream, type ThreadStreamOptions, type ThreadTask, type ToolCallFromTool, type ToolCallState, type ToolCallWithResult, type ToolCallsFromTools, type ToolEvent, type ToolMessage, type ToolProgress, type ToolsStreamEvent, type TransportAdapter, type UnwrapExtension, type UpdatesStreamEvent, UseAgentStream, UseAgentStreamOptions, UseDeepAgentStream, UseDeepAgentStreamOptions, type ValuesStreamEvent, type VideoMedia, executeHeadlessTool, filterOutHeadlessToolInterrupts, findHeadlessTool, flushPendingHeadlessToolInterrupts, getApiKey, handleHeadlessToolInterrupt, headlessToolResumeCommand, isHeadlessToolInterrupt, overrideFetchImplementation, parseHeadlessToolInterruptPayload };
26
+ export { type AIMessage, type AgentServerAdapter, type AnyHeadlessToolImplementation, type AnyMediaHandle, type AssembledMessage, type Assistant, type AssistantBase, type AssistantGraph, type AssistantVersion, type AssistantsSearchResponse, type AudioMedia, type BagTemplate, BaseStream, type Checkpoint, Client, type ClientConfig, type Command, type Config, type Cron, type CronCreateForThreadResponse, type CronCreateResponse, type CustomStreamEvent, type DebugStreamEvent, type DefaultToolCall, type DefaultValues, type ErrorStreamEvent, type EventSubscription, type EventsStreamEvent, type FeedbackStreamEvent, type FileMedia, type FlushPendingHeadlessToolInterruptsOptions, type FunctionMessage, type GraphSchema, type HeadlessToolImplementation, type HeadlessToolInterrupt, HttpAgentServerAdapter, type HttpAgentServerAdapterOptions, type HumanMessage, type ImageMedia, InferBag, InferNodeNames, InferNodeReturnTypes, InferStateType, InferSubagentStates, InferToolCalls, type InferToolOutput, type Interrupt, type Item, type ListNamespaceResponse, MediaAssembler, type MediaAssemblerCallbacks, type MediaAssemblerOptions, MediaAssemblyError, type MediaAssemblyErrorKind, type MediaBase, type MediaBlockType, type Message, MessageAssembler, type MessageSubscription, type MessagesStreamEvent, type MessagesTupleStreamEvent, type Metadata, type MetadataStreamEvent, NAMESPACE_SEPARATOR, type OnConflictBehavior, type OnToolCallback, ProtocolError, type ProtocolRequestHook, ProtocolSseTransportAdapter, type ProtocolSseTransportOptions, type ProtocolTransportPaths, ProtocolWebSocketTransportAdapter, type ProtocolWebSocketTransportOptions, type RemoveMessage, type RequestHook, ResolveStreamInterface, ResolveStreamOptions, type Run, type RunsInvokePayload, type SearchItem, type SearchItemsResponse, type SessionOrderingState, StateRecord, StreamError, type StreamEvent, type StreamMode, SubscriptionHandle, type SystemMessage, type Thread, type ThreadExtension, type ThreadExtensions, type ThreadState, type ThreadStatus, ThreadStream, type ThreadStreamOptions, type ThreadTask, type ToolCallFromTool, type ToolCallState, type ToolCallWithResult, type ToolCallsFromTools, type ToolEvent, type ToolMessage, type ToolProgress, type ToolsStreamEvent, type TransportAdapter, type UnwrapExtension, type UpdatesStreamEvent, UseAgentStream, UseAgentStreamOptions, UseDeepAgentStream, UseDeepAgentStreamOptions, type ValuesStreamEvent, type VideoMedia, executeHeadlessTool, filterOutHeadlessToolInterrupts, findHeadlessTool, flushPendingHeadlessToolInterrupts, getApiKey, handleHeadlessToolInterrupt, headlessToolResumeCommand, isHeadlessToolInterrupt, overrideFetchImplementation, parseHeadlessToolInterruptPayload, scheduleCoalescedHeadlessToolFlush };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Assistant, AssistantBase, AssistantGraph, AssistantVersion, AssistantsSearchResponse, Checkpoint, Config, Cron, CronCreateForThreadResponse, CronCreateResponse, DefaultValues, GraphSchema, Interrupt, Item, ListNamespaceResponse, Metadata, Run, SearchItem, SearchItemsResponse, Thread, ThreadState, ThreadStatus, ThreadTask } from "./schema.js";
2
- import { AIMessage, DefaultToolCall, FunctionMessage, HumanMessage, Message, RemoveMessage, SystemMessage, ToolCallFromTool, ToolCallState, ToolCallWithResult, ToolCallsFromTools, ToolMessage } from "./types.messages.js";
2
+ import { AIMessage, DefaultToolCall, FunctionMessage, HumanMessage, InferToolOutput, Message, RemoveMessage, SystemMessage, ToolCallFromTool, ToolCallState, ToolCallWithResult, ToolCallsFromTools, ToolMessage } from "./types.messages.js";
3
3
  import { CustomStreamEvent, DebugStreamEvent, ErrorStreamEvent, EventsStreamEvent, FeedbackStreamEvent, MessagesStreamEvent, MessagesTupleStreamEvent, MetadataStreamEvent, StreamMode, ToolProgress, ToolsStreamEvent, UpdatesStreamEvent, ValuesStreamEvent } from "./types.stream.js";
4
4
  import { Command, OnConflictBehavior, RunsInvokePayload, StreamEvent } from "./types.js";
5
5
  import { ClientConfig, RequestHook, getApiKey } from "./client/base.js";
@@ -16,11 +16,11 @@ import { HttpAgentServerAdapter, HttpAgentServerAdapterOptions } from "./client/
16
16
  import { Client } from "./client/index.js";
17
17
  import { overrideFetchImplementation } from "./singletons/fetch.js";
18
18
  import { BagTemplate } from "./types.template.js";
19
- import { AnyHeadlessToolImplementation, FlushPendingHeadlessToolInterruptsOptions, HeadlessToolImplementation, HeadlessToolInterrupt, OnToolCallback, ToolEvent, executeHeadlessTool, filterOutHeadlessToolInterrupts, findHeadlessTool, flushPendingHeadlessToolInterrupts, handleHeadlessToolInterrupt, headlessToolResumeCommand, isHeadlessToolInterrupt, parseHeadlessToolInterruptPayload } from "./headless-tools.js";
19
+ import { AnyHeadlessToolImplementation, FlushPendingHeadlessToolInterruptsOptions, HeadlessToolImplementation, HeadlessToolInterrupt, OnToolCallback, ToolEvent, executeHeadlessTool, filterOutHeadlessToolInterrupts, findHeadlessTool, flushPendingHeadlessToolInterrupts, handleHeadlessToolInterrupt, headlessToolResumeCommand, isHeadlessToolInterrupt, parseHeadlessToolInterruptPayload, scheduleCoalescedHeadlessToolFlush } from "./headless-tools.js";
20
20
  import { BaseStream, StateRecord } from "./ui/stream/base.js";
21
21
  import { UseAgentStream, UseAgentStreamOptions } from "./ui/stream/agent.js";
22
22
  import { UseDeepAgentStream, UseDeepAgentStreamOptions } from "./ui/stream/deep-agent.js";
23
23
  import { InferBag, InferNodeNames, InferNodeReturnTypes, InferStateType, InferSubagentStates, InferToolCalls, ResolveStreamInterface, ResolveStreamOptions } from "./ui/stream/index.js";
24
24
  import { NAMESPACE_SEPARATOR } from "./stream/constants.js";
25
25
  import { StreamError } from "./ui/errors.js";
26
- export { type AIMessage, type AgentServerAdapter, type AnyHeadlessToolImplementation, type AnyMediaHandle, type AssembledMessage, type Assistant, type AssistantBase, type AssistantGraph, type AssistantVersion, type AssistantsSearchResponse, type AudioMedia, type BagTemplate, BaseStream, type Checkpoint, Client, type ClientConfig, type Command, type Config, type Cron, type CronCreateForThreadResponse, type CronCreateResponse, type CustomStreamEvent, type DebugStreamEvent, type DefaultToolCall, type DefaultValues, type ErrorStreamEvent, type EventSubscription, type EventsStreamEvent, type FeedbackStreamEvent, type FileMedia, type FlushPendingHeadlessToolInterruptsOptions, type FunctionMessage, type GraphSchema, type HeadlessToolImplementation, type HeadlessToolInterrupt, HttpAgentServerAdapter, type HttpAgentServerAdapterOptions, type HumanMessage, type ImageMedia, InferBag, InferNodeNames, InferNodeReturnTypes, InferStateType, InferSubagentStates, InferToolCalls, type Interrupt, type Item, type ListNamespaceResponse, MediaAssembler, type MediaAssemblerCallbacks, type MediaAssemblerOptions, MediaAssemblyError, type MediaAssemblyErrorKind, type MediaBase, type MediaBlockType, type Message, MessageAssembler, type MessageSubscription, type MessagesStreamEvent, type MessagesTupleStreamEvent, type Metadata, type MetadataStreamEvent, NAMESPACE_SEPARATOR, type OnConflictBehavior, type OnToolCallback, ProtocolError, type ProtocolRequestHook, ProtocolSseTransportAdapter, type ProtocolSseTransportOptions, type ProtocolTransportPaths, ProtocolWebSocketTransportAdapter, type ProtocolWebSocketTransportOptions, type RemoveMessage, type RequestHook, ResolveStreamInterface, ResolveStreamOptions, type Run, type RunsInvokePayload, type SearchItem, type SearchItemsResponse, type SessionOrderingState, StateRecord, StreamError, type StreamEvent, type StreamMode, SubscriptionHandle, type SystemMessage, type Thread, type ThreadExtension, type ThreadExtensions, type ThreadState, type ThreadStatus, ThreadStream, type ThreadStreamOptions, type ThreadTask, type ToolCallFromTool, type ToolCallState, type ToolCallWithResult, type ToolCallsFromTools, type ToolEvent, type ToolMessage, type ToolProgress, type ToolsStreamEvent, type TransportAdapter, type UnwrapExtension, type UpdatesStreamEvent, UseAgentStream, UseAgentStreamOptions, UseDeepAgentStream, UseDeepAgentStreamOptions, type ValuesStreamEvent, type VideoMedia, executeHeadlessTool, filterOutHeadlessToolInterrupts, findHeadlessTool, flushPendingHeadlessToolInterrupts, getApiKey, handleHeadlessToolInterrupt, headlessToolResumeCommand, isHeadlessToolInterrupt, overrideFetchImplementation, parseHeadlessToolInterruptPayload };
26
+ export { type AIMessage, type AgentServerAdapter, type AnyHeadlessToolImplementation, type AnyMediaHandle, type AssembledMessage, type Assistant, type AssistantBase, type AssistantGraph, type AssistantVersion, type AssistantsSearchResponse, type AudioMedia, type BagTemplate, BaseStream, type Checkpoint, Client, type ClientConfig, type Command, type Config, type Cron, type CronCreateForThreadResponse, type CronCreateResponse, type CustomStreamEvent, type DebugStreamEvent, type DefaultToolCall, type DefaultValues, type ErrorStreamEvent, type EventSubscription, type EventsStreamEvent, type FeedbackStreamEvent, type FileMedia, type FlushPendingHeadlessToolInterruptsOptions, type FunctionMessage, type GraphSchema, type HeadlessToolImplementation, type HeadlessToolInterrupt, HttpAgentServerAdapter, type HttpAgentServerAdapterOptions, type HumanMessage, type ImageMedia, InferBag, InferNodeNames, InferNodeReturnTypes, InferStateType, InferSubagentStates, InferToolCalls, type InferToolOutput, type Interrupt, type Item, type ListNamespaceResponse, MediaAssembler, type MediaAssemblerCallbacks, type MediaAssemblerOptions, MediaAssemblyError, type MediaAssemblyErrorKind, type MediaBase, type MediaBlockType, type Message, MessageAssembler, type MessageSubscription, type MessagesStreamEvent, type MessagesTupleStreamEvent, type Metadata, type MetadataStreamEvent, NAMESPACE_SEPARATOR, type OnConflictBehavior, type OnToolCallback, ProtocolError, type ProtocolRequestHook, ProtocolSseTransportAdapter, type ProtocolSseTransportOptions, type ProtocolTransportPaths, ProtocolWebSocketTransportAdapter, type ProtocolWebSocketTransportOptions, type RemoveMessage, type RequestHook, ResolveStreamInterface, ResolveStreamOptions, type Run, type RunsInvokePayload, type SearchItem, type SearchItemsResponse, type SessionOrderingState, StateRecord, StreamError, type StreamEvent, type StreamMode, SubscriptionHandle, type SystemMessage, type Thread, type ThreadExtension, type ThreadExtensions, type ThreadState, type ThreadStatus, ThreadStream, type ThreadStreamOptions, type ThreadTask, type ToolCallFromTool, type ToolCallState, type ToolCallWithResult, type ToolCallsFromTools, type ToolEvent, type ToolMessage, type ToolProgress, type ToolsStreamEvent, type TransportAdapter, type UnwrapExtension, type UpdatesStreamEvent, UseAgentStream, UseAgentStreamOptions, UseDeepAgentStream, UseDeepAgentStreamOptions, type ValuesStreamEvent, type VideoMedia, executeHeadlessTool, filterOutHeadlessToolInterrupts, findHeadlessTool, flushPendingHeadlessToolInterrupts, getApiKey, handleHeadlessToolInterrupt, headlessToolResumeCommand, isHeadlessToolInterrupt, overrideFetchImplementation, parseHeadlessToolInterruptPayload, scheduleCoalescedHeadlessToolFlush };
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { MessageAssembler } from "./client/stream/messages.js";
4
4
  import { MediaAssembler, MediaAssemblyError } from "./client/stream/media.js";
5
5
  import { ProtocolError } from "./client/stream/error.js";
6
6
  import { NAMESPACE_SEPARATOR } from "./stream/constants.js";
7
- import { executeHeadlessTool, filterOutHeadlessToolInterrupts, findHeadlessTool, flushPendingHeadlessToolInterrupts, handleHeadlessToolInterrupt, headlessToolResumeCommand, isHeadlessToolInterrupt, parseHeadlessToolInterruptPayload } from "./headless-tools.js";
7
+ import { executeHeadlessTool, filterOutHeadlessToolInterrupts, findHeadlessTool, flushPendingHeadlessToolInterrupts, handleHeadlessToolInterrupt, headlessToolResumeCommand, isHeadlessToolInterrupt, parseHeadlessToolInterruptPayload, scheduleCoalescedHeadlessToolFlush } from "./headless-tools.js";
8
8
  import { SubscriptionHandle, ThreadStream } from "./client/stream/index.js";
9
9
  import { ProtocolSseTransportAdapter } from "./client/stream/transport/http.js";
10
10
  import { ProtocolWebSocketTransportAdapter } from "./client/stream/transport/websocket.js";
@@ -13,4 +13,4 @@ import { Client } from "./client/index.js";
13
13
  import "./client.js";
14
14
  import "./stream/index.js";
15
15
  import { StreamError } from "./ui/errors.js";
16
- export { Client, HttpAgentServerAdapter, MediaAssembler, MediaAssemblyError, MessageAssembler, NAMESPACE_SEPARATOR, ProtocolError, ProtocolSseTransportAdapter, ProtocolWebSocketTransportAdapter, StreamError, SubscriptionHandle, ThreadStream, executeHeadlessTool, filterOutHeadlessToolInterrupts, findHeadlessTool, flushPendingHeadlessToolInterrupts, getApiKey, handleHeadlessToolInterrupt, headlessToolResumeCommand, isHeadlessToolInterrupt, overrideFetchImplementation, parseHeadlessToolInterruptPayload };
16
+ export { Client, HttpAgentServerAdapter, MediaAssembler, MediaAssemblyError, MessageAssembler, NAMESPACE_SEPARATOR, ProtocolError, ProtocolSseTransportAdapter, ProtocolWebSocketTransportAdapter, StreamError, SubscriptionHandle, ThreadStream, executeHeadlessTool, filterOutHeadlessToolInterrupts, findHeadlessTool, flushPendingHeadlessToolInterrupts, getApiKey, handleHeadlessToolInterrupt, headlessToolResumeCommand, isHeadlessToolInterrupt, overrideFetchImplementation, parseHeadlessToolInterruptPayload, scheduleCoalescedHeadlessToolFlush };