@librechat/agents 3.9.6 → 3.9.8
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.
- package/dist/cjs/common/enum.cjs +1 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +4 -3
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/instrumentation.cjs +3 -3
- package/dist/cjs/langfuse.cjs +49 -5
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/langfuseRuntimeScope.cjs +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +2 -2
- package/dist/cjs/llm/invoke.cjs +1 -1
- package/dist/cjs/run.cjs +39 -15
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +7 -4
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +1 -1
- package/dist/cjs/tools/ToolNode.cjs +46 -11
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/handlers.cjs +2 -2
- package/dist/cjs/tools/handlers.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +5 -1
- package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +74 -6
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/types/hitl.cjs +4 -0
- package/dist/cjs/types/hitl.cjs.map +1 -1
- package/dist/cjs/utils/events.cjs +20 -2
- package/dist/cjs/utils/events.cjs.map +1 -1
- package/dist/esm/common/enum.mjs +1 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +4 -3
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/instrumentation.mjs +3 -3
- package/dist/esm/langfuse.mjs +50 -7
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/langfuseRuntimeScope.mjs +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +2 -2
- package/dist/esm/llm/invoke.mjs +1 -1
- package/dist/esm/run.mjs +40 -16
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +11 -7
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +1 -1
- package/dist/esm/tools/ToolNode.mjs +50 -14
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/handlers.mjs +2 -2
- package/dist/esm/tools/handlers.mjs.map +1 -1
- package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +5 -1
- package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +75 -7
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/types/hitl.mjs +4 -1
- package/dist/esm/types/hitl.mjs.map +1 -1
- package/dist/esm/utils/events.mjs +20 -3
- package/dist/esm/utils/events.mjs.map +1 -1
- package/dist/types/common/enum.d.ts +2 -0
- package/dist/types/graphs/Graph.d.ts +2 -2
- package/dist/types/langfuse.d.ts +19 -0
- package/dist/types/tools/ToolNode.d.ts +1 -0
- package/dist/types/tools/handlers.d.ts +2 -1
- package/dist/types/types/graph.d.ts +3 -3
- package/dist/types/types/hitl.d.ts +11 -0
- package/dist/types/types/stream.d.ts +24 -0
- package/dist/types/types/tools.d.ts +4 -0
- package/dist/types/utils/events.d.ts +4 -0
- package/package.json +1 -1
- package/src/common/enum.ts +2 -0
- package/src/graphs/Graph.ts +5 -2
- package/src/langfuse.ts +100 -3
- package/src/run.ts +41 -23
- package/src/stream.ts +12 -3
- package/src/tools/ToolNode.ts +108 -17
- package/src/tools/handlers.ts +4 -1
- package/src/tools/local/LocalProgrammaticToolCalling.ts +16 -4
- package/src/tools/subagent/SubagentExecutor.ts +129 -10
- package/src/types/graph.ts +3 -0
- package/src/types/hitl.ts +20 -0
- package/src/types/stream.ts +21 -0
- package/src/types/tools.ts +4 -0
- package/src/utils/events.ts +21 -2
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
END,
|
|
14
14
|
GraphInterrupt,
|
|
15
15
|
INTERRUPT,
|
|
16
|
+
MemorySaver,
|
|
16
17
|
MessagesAnnotation,
|
|
17
18
|
START,
|
|
18
19
|
StateGraph,
|
|
@@ -141,9 +142,10 @@ import { seedAgentInitialSessions } from '@/utils/toolSessions';
|
|
|
141
142
|
import { stableStringify } from '@/tools/eagerEventExecution';
|
|
142
143
|
import { convertInjectedMessages } from '@/messages/injected';
|
|
143
144
|
import { resolveClientOptionsModel } from '@/llm/request';
|
|
145
|
+
import { isBackgroundDenyMode } from '@/types/hitl';
|
|
144
146
|
import { composeAbortSignals } from '@/utils/misc';
|
|
145
|
-
import { sleep } from '@/utils/run';
|
|
146
147
|
import { HandlerRegistry } from '@/events';
|
|
148
|
+
import { sleep } from '@/utils/run';
|
|
147
149
|
|
|
148
150
|
export {
|
|
149
151
|
buildChildInputs,
|
|
@@ -875,6 +877,27 @@ function createSubagentFailure(
|
|
|
875
877
|
return { content, messages: [], error };
|
|
876
878
|
}
|
|
877
879
|
|
|
880
|
+
function excludeBackgroundQuestionTool(agentInputs: AgentInputs): void {
|
|
881
|
+
const questionTool = 'ask_user_question';
|
|
882
|
+
agentInputs.tools = agentInputs.tools?.filter(
|
|
883
|
+
(tool) => !('name' in tool) || tool.name !== questionTool
|
|
884
|
+
);
|
|
885
|
+
agentInputs.graphTools = agentInputs.graphTools?.filter(
|
|
886
|
+
(tool) => tool.name !== questionTool
|
|
887
|
+
);
|
|
888
|
+
agentInputs.toolDefinitions = agentInputs.toolDefinitions?.filter(
|
|
889
|
+
(tool) => tool.name !== questionTool
|
|
890
|
+
);
|
|
891
|
+
if (agentInputs.toolMap?.has(questionTool) === true) {
|
|
892
|
+
agentInputs.toolMap = new Map(agentInputs.toolMap);
|
|
893
|
+
agentInputs.toolMap.delete(questionTool);
|
|
894
|
+
}
|
|
895
|
+
if (agentInputs.toolRegistry?.has(questionTool) === true) {
|
|
896
|
+
agentInputs.toolRegistry = new Map(agentInputs.toolRegistry);
|
|
897
|
+
agentInputs.toolRegistry.delete(questionTool);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
878
901
|
/**
|
|
879
902
|
* Factory that constructs a child graph for subagent execution. Injected
|
|
880
903
|
* rather than imported so that `SubagentExecutor` does not have a runtime
|
|
@@ -1106,7 +1129,10 @@ export class SubagentExecutor {
|
|
|
1106
1129
|
message: 'Maximum subagent nesting depth exceeded.',
|
|
1107
1130
|
});
|
|
1108
1131
|
}
|
|
1109
|
-
if (
|
|
1132
|
+
if (
|
|
1133
|
+
this.humanInTheLoop?.enabled === true &&
|
|
1134
|
+
this.humanInTheLoop.backgroundPausePolicy === 'reject'
|
|
1135
|
+
) {
|
|
1110
1136
|
return JSON.stringify({
|
|
1111
1137
|
status: 'rejected',
|
|
1112
1138
|
message:
|
|
@@ -1292,6 +1318,15 @@ export class SubagentExecutor {
|
|
|
1292
1318
|
usageSink: this.usageSink,
|
|
1293
1319
|
subagentContext: this.subagentContext,
|
|
1294
1320
|
streamLimits: this.streamLimits,
|
|
1321
|
+
humanInTheLoop:
|
|
1322
|
+
this.humanInTheLoop?.enabled === true ||
|
|
1323
|
+
this.humanInTheLoop?.backgroundPausePolicy === 'deny'
|
|
1324
|
+
? {
|
|
1325
|
+
enabled: false,
|
|
1326
|
+
backgroundPausePolicy: 'deny',
|
|
1327
|
+
backgroundDeny: true,
|
|
1328
|
+
}
|
|
1329
|
+
: this.humanInTheLoop,
|
|
1295
1330
|
maxDepth: this.maxDepth,
|
|
1296
1331
|
createChildGraph:
|
|
1297
1332
|
detachedGraphFactory == null
|
|
@@ -1338,6 +1373,27 @@ export class SubagentExecutor {
|
|
|
1338
1373
|
if (result.error != null) {
|
|
1339
1374
|
throw new Error(result.error);
|
|
1340
1375
|
}
|
|
1376
|
+
if (this.humanInTheLoop?.enabled === true) {
|
|
1377
|
+
const deniedTools = new Set<string>();
|
|
1378
|
+
for (const message of result.messages) {
|
|
1379
|
+
if (
|
|
1380
|
+
message instanceof ToolMessage &&
|
|
1381
|
+
message.status === 'error' &&
|
|
1382
|
+
typeof message.content === 'string' &&
|
|
1383
|
+
message.content.startsWith('Blocked: Approval required for "') &&
|
|
1384
|
+
message.name != null
|
|
1385
|
+
) {
|
|
1386
|
+
deniedTools.add(message.name);
|
|
1387
|
+
if (deniedTools.size >= 10) break;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
if (deniedTools.size > 0) {
|
|
1391
|
+
return {
|
|
1392
|
+
...result,
|
|
1393
|
+
content: `${result.content}\nBackground approval denied for: ${[...deniedTools].join(', ')}. Ask the parent to run those tools in the foreground.`,
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1341
1397
|
return result;
|
|
1342
1398
|
} finally {
|
|
1343
1399
|
detached.clearHeavyState();
|
|
@@ -2489,6 +2545,11 @@ export class SubagentExecutor {
|
|
|
2489
2545
|
parentMaxDepth: this.maxDepth,
|
|
2490
2546
|
keepToolDefinitions: hasToolExecuteHandler,
|
|
2491
2547
|
});
|
|
2548
|
+
if (isBackgroundDenyMode(this.humanInTheLoop)) {
|
|
2549
|
+
for (const agentInputs of childPlan.agents) {
|
|
2550
|
+
excludeBackgroundQuestionTool(agentInputs);
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2492
2553
|
const childAgentId = childPlan.subjectAgentId;
|
|
2493
2554
|
const currentHookSessionId =
|
|
2494
2555
|
asNonEmptyString(params.hookSessionId) ??
|
|
@@ -2638,6 +2699,18 @@ export class SubagentExecutor {
|
|
|
2638
2699
|
});
|
|
2639
2700
|
}
|
|
2640
2701
|
childGraph ??= this.createChildGraph(childGraphInput);
|
|
2702
|
+
if (isBackgroundDenyMode(this.humanInTheLoop)) {
|
|
2703
|
+
childGraph.humanInTheLoop = this.humanInTheLoop;
|
|
2704
|
+
childGraph.eagerEventToolExecution = undefined;
|
|
2705
|
+
// Never reuse the HITL parent's saver: its checkpoint namespace may
|
|
2706
|
+
// contain parent messages. A private saver also lets an unexpected
|
|
2707
|
+
// interrupt surface as a task error instead of a missing-saver tool
|
|
2708
|
+
// error that the child could mistake for a completed task.
|
|
2709
|
+
childGraph.compileOptions = {
|
|
2710
|
+
...childGraph.compileOptions,
|
|
2711
|
+
checkpointer: new MemorySaver(),
|
|
2712
|
+
};
|
|
2713
|
+
}
|
|
2641
2714
|
if (params.taskRuntime != null) {
|
|
2642
2715
|
childGraph.hookRegistry = this.hookRegistry;
|
|
2643
2716
|
}
|
|
@@ -2756,7 +2829,8 @@ export class SubagentExecutor {
|
|
|
2756
2829
|
childConfigurable[SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY] = resumeAttemptId;
|
|
2757
2830
|
}
|
|
2758
2831
|
childConfigurable.thread_id =
|
|
2759
|
-
this.humanInTheLoop?.enabled === true
|
|
2832
|
+
this.humanInTheLoop?.enabled === true ||
|
|
2833
|
+
isBackgroundDenyMode(this.humanInTheLoop)
|
|
2760
2834
|
? childThreadId
|
|
2761
2835
|
: (inheritedConfigurable.thread_id ?? childRunId);
|
|
2762
2836
|
const childInvokeConfig = {
|
|
@@ -2884,16 +2958,31 @@ export class SubagentExecutor {
|
|
|
2884
2958
|
},
|
|
2885
2959
|
sessionId: currentHookSessionId,
|
|
2886
2960
|
matchQuery: subagentType,
|
|
2887
|
-
}).catch((): AggregatedHookResult =>
|
|
2961
|
+
}).catch((): AggregatedHookResult =>
|
|
2962
|
+
isBackgroundDenyMode(this.humanInTheLoop)
|
|
2963
|
+
? { ...HOOK_FALLBACK, hasHookFailures: true }
|
|
2964
|
+
: HOOK_FALLBACK
|
|
2965
|
+
);
|
|
2888
2966
|
|
|
2889
|
-
|
|
2967
|
+
const policyFailed =
|
|
2968
|
+
hookResult.hasHookFailures === true &&
|
|
2969
|
+
isBackgroundDenyMode(this.humanInTheLoop);
|
|
2970
|
+
if (
|
|
2971
|
+
policyFailed ||
|
|
2972
|
+
hookResult.decision === 'deny' ||
|
|
2973
|
+
hookResult.decision === 'ask'
|
|
2974
|
+
) {
|
|
2890
2975
|
this.clearChildGraph(childGraph);
|
|
2891
2976
|
execution.releaseActiveRun();
|
|
2892
2977
|
this.executions.remove(execution);
|
|
2893
|
-
return
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2978
|
+
return policyFailed
|
|
2979
|
+
? createSubagentFailure(
|
|
2980
|
+
'Subagent start policy could not be evaluated; background execution denied.'
|
|
2981
|
+
)
|
|
2982
|
+
: {
|
|
2983
|
+
content: `Blocked: ${hookResult.reason ?? 'Blocked by hook'}`,
|
|
2984
|
+
messages: [],
|
|
2985
|
+
};
|
|
2897
2986
|
}
|
|
2898
2987
|
}
|
|
2899
2988
|
execution.markStarted();
|
|
@@ -2956,7 +3045,8 @@ export class SubagentExecutor {
|
|
|
2956
3045
|
};
|
|
2957
3046
|
}
|
|
2958
3047
|
}
|
|
2959
|
-
} catch (
|
|
3048
|
+
} catch (caught) {
|
|
3049
|
+
let error = caught;
|
|
2960
3050
|
/** Stamped at failure, not after the error-envelope work below. */
|
|
2961
3051
|
const childTerminalAt = Date.now();
|
|
2962
3052
|
/**
|
|
@@ -2971,6 +3061,11 @@ export class SubagentExecutor {
|
|
|
2971
3061
|
* `cancelled` here.
|
|
2972
3062
|
*/
|
|
2973
3063
|
const abortedBeforeError = childSignal.aborted;
|
|
3064
|
+
if (isGraphInterrupt(error) && params.taskRuntime != null) {
|
|
3065
|
+
error = new Error(
|
|
3066
|
+
'Background subagent cannot pause for human input. Run the tool in the foreground.'
|
|
3067
|
+
);
|
|
3068
|
+
}
|
|
2974
3069
|
if (isGraphInterrupt(error)) {
|
|
2975
3070
|
const activeChildRun = execution.activeRun;
|
|
2976
3071
|
if (activeChildRun != null) {
|
|
@@ -3368,6 +3463,10 @@ export class SubagentExecutor {
|
|
|
3368
3463
|
scheduleWrap(eventName, 'run_step', data, memberAgentId);
|
|
3369
3464
|
return;
|
|
3370
3465
|
}
|
|
3466
|
+
if (eventName === GraphEvents.ON_TOOL_CALLS_DISPATCHED) {
|
|
3467
|
+
scheduleWrap(eventName, 'tool_calls_dispatched', data, memberAgentId);
|
|
3468
|
+
return;
|
|
3469
|
+
}
|
|
3371
3470
|
if (eventName === GraphEvents.ON_RUN_STEP_DELTA) {
|
|
3372
3471
|
scheduleWrap(eventName, 'run_step_delta', data, memberAgentId);
|
|
3373
3472
|
return;
|
|
@@ -3721,6 +3820,26 @@ export function sanitizeForwardedSubagentUpdateData(
|
|
|
3721
3820
|
if (eventName === GraphEvents.ON_RUN_STEP_DELTA) {
|
|
3722
3821
|
return sanitizeRunStepDeltaUpdateData(data);
|
|
3723
3822
|
}
|
|
3823
|
+
if (eventName === GraphEvents.ON_TOOL_CALLS_DISPATCHED) {
|
|
3824
|
+
if (!isObjectLike(data)) return undefined;
|
|
3825
|
+
const event = data as { dispatched_at?: number; toolCalls?: unknown };
|
|
3826
|
+
if (!Array.isArray(event.toolCalls) || typeof event.dispatched_at !== 'number') {
|
|
3827
|
+
return undefined;
|
|
3828
|
+
}
|
|
3829
|
+
return {
|
|
3830
|
+
dispatched_at: event.dispatched_at,
|
|
3831
|
+
toolCalls: (event.toolCalls as unknown[]).flatMap((call) => {
|
|
3832
|
+
if (!isObjectLike(call)) return [];
|
|
3833
|
+
const entry = call as Record<string, unknown>;
|
|
3834
|
+
if (typeof entry.id !== 'string' || typeof entry.name !== 'string') return [];
|
|
3835
|
+
return [{
|
|
3836
|
+
id: entry.id,
|
|
3837
|
+
name: entry.name,
|
|
3838
|
+
...(typeof entry.stepId === 'string' ? { stepId: entry.stepId } : {}),
|
|
3839
|
+
}];
|
|
3840
|
+
}),
|
|
3841
|
+
};
|
|
3842
|
+
}
|
|
3724
3843
|
if (eventName === GraphEvents.ON_RUN_STEP_COMPLETED) {
|
|
3725
3844
|
return sanitizeRunStepCompletedUpdateData(data);
|
|
3726
3845
|
}
|
package/src/types/graph.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
RunStepDeltaEvent,
|
|
18
18
|
RunStepResumeState,
|
|
19
19
|
RunStepClosedEvent,
|
|
20
|
+
ToolCallsDispatchedEvent,
|
|
20
21
|
MessageDeltaEvent,
|
|
21
22
|
ReasoningDeltaEvent,
|
|
22
23
|
} from '@/types/stream';
|
|
@@ -209,6 +210,7 @@ export interface EventHandler {
|
|
|
209
210
|
| RunStep
|
|
210
211
|
| RunStepDeltaEvent
|
|
211
212
|
| RunStepClosedEvent
|
|
213
|
+
| ToolCallsDispatchedEvent
|
|
212
214
|
| MessageDeltaEvent
|
|
213
215
|
| ReasoningDeltaEvent
|
|
214
216
|
| SummarizeStartEvent
|
|
@@ -694,6 +696,7 @@ export type SubagentUpdatePhase =
|
|
|
694
696
|
| 'start'
|
|
695
697
|
| 'run_step'
|
|
696
698
|
| 'run_step_delta'
|
|
699
|
+
| 'tool_calls_dispatched'
|
|
697
700
|
| 'run_step_completed'
|
|
698
701
|
| 'run_step_closed'
|
|
699
702
|
| 'message_delta'
|
package/src/types/hitl.ts
CHANGED
|
@@ -407,6 +407,26 @@ export interface HumanInTheLoopConfig {
|
|
|
407
407
|
* UI is ready to render and resolve `tool_approval` interrupts.
|
|
408
408
|
*/
|
|
409
409
|
enabled?: boolean;
|
|
410
|
+
/**
|
|
411
|
+
* Background children cannot pause for human input. `deny` (the default)
|
|
412
|
+
* lets them run but blocks tools whose hooks request approval, and excludes
|
|
413
|
+
* `ask_user_question`. `reject` preserves the legacy admission check.
|
|
414
|
+
* Durable deferred approvals require a separate resume implementation.
|
|
415
|
+
*/
|
|
416
|
+
backgroundPausePolicy?: 'deny' | 'reject';
|
|
417
|
+
/** @internal Set only on detached children and their descendants, never on the parent run. */
|
|
418
|
+
backgroundDeny?: true;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** True only on an SDK-created background child, not a foreground HITL run. */
|
|
422
|
+
export function isBackgroundDenyMode(
|
|
423
|
+
config: HumanInTheLoopConfig | undefined
|
|
424
|
+
): boolean {
|
|
425
|
+
return (
|
|
426
|
+
config?.backgroundDeny === true &&
|
|
427
|
+
config.enabled === false &&
|
|
428
|
+
config.backgroundPausePolicy === 'deny'
|
|
429
|
+
);
|
|
410
430
|
}
|
|
411
431
|
|
|
412
432
|
/**
|
package/src/types/stream.ts
CHANGED
|
@@ -158,6 +158,8 @@ export interface RunStepDeltaEvent {
|
|
|
158
158
|
* The delta containing the fields that have changed on the run step.
|
|
159
159
|
*/
|
|
160
160
|
delta: ToolCallDelta;
|
|
161
|
+
/** Epoch ms when the SDK received this tool-call fragment, before step dispatch. */
|
|
162
|
+
observed_at?: number;
|
|
161
163
|
}
|
|
162
164
|
|
|
163
165
|
/**
|
|
@@ -264,6 +266,25 @@ export type ToolCallCompleted = {
|
|
|
264
266
|
tool_call: ProcessedToolCall;
|
|
265
267
|
};
|
|
266
268
|
|
|
269
|
+
/**
|
|
270
|
+
* A tool call has crossed from model-authored argument preparation into SDK
|
|
271
|
+
* execution. Emitted immediately before host dispatch or direct invocation;
|
|
272
|
+
* it does not claim the host has begun its own work. Paired with the tool
|
|
273
|
+
* completion `completed_at`, the stamp measures SDK handoff-to-result time,
|
|
274
|
+
* not MCP or database execution time. The earliest `observed_at` tool-call
|
|
275
|
+
* delta for a call bounds its argument preparation time; consumers correlate
|
|
276
|
+
* deltas by step/index until an ID is known. For a call without streamed
|
|
277
|
+
* fragments, the preparation start is unknown. Never infer a missing start.
|
|
278
|
+
* An eager call may dispatch before it has a step ID, and this observational
|
|
279
|
+
* event may arrive after completion: correlate by tool call ID and timestamps,
|
|
280
|
+
* not arrival order.
|
|
281
|
+
*/
|
|
282
|
+
export type ToolCallsDispatchedEvent = {
|
|
283
|
+
dispatched_at: number;
|
|
284
|
+
runId?: string;
|
|
285
|
+
toolCalls: Array<{ id: string; name: string; stepId?: string }>;
|
|
286
|
+
};
|
|
287
|
+
|
|
267
288
|
export type ToolCompleteEvent = ToolCallCompleted & {
|
|
268
289
|
/** The Step Id of the Tool Call */
|
|
269
290
|
id: string;
|
package/src/types/tools.ts
CHANGED
|
@@ -700,6 +700,8 @@ export type OutcomePatch = {
|
|
|
700
700
|
export type ToolExecuteResult = {
|
|
701
701
|
/** Matches ToolCallRequest.id */
|
|
702
702
|
toolCallId: string;
|
|
703
|
+
/** SDK-stamped result receipt time; never host-supplied or inferred from handler cleanup. */
|
|
704
|
+
received_at?: number;
|
|
703
705
|
/** Tool output content */
|
|
704
706
|
content: string | unknown[];
|
|
705
707
|
/** Optional artifact (for content_and_artifact format) */
|
|
@@ -1256,6 +1258,8 @@ export type ProgrammaticCache = {
|
|
|
1256
1258
|
|
|
1257
1259
|
export type ProgrammaticHookContext = {
|
|
1258
1260
|
registry: import('@/hooks').HookRegistry | undefined;
|
|
1261
|
+
/** A detached HITL child must never execute an inner tool if its policy hook fails. */
|
|
1262
|
+
failClosedOnHookError?: boolean;
|
|
1259
1263
|
runId: string;
|
|
1260
1264
|
threadId?: string;
|
|
1261
1265
|
agentId?: string;
|
package/src/utils/events.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// src/utils/events.ts
|
|
3
3
|
import { dispatchCustomEvent } from '@langchain/core/callbacks/dispatch';
|
|
4
4
|
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
5
|
+
import type { ToolCallsDispatchedEvent } from '@/types/stream';
|
|
5
6
|
import type { ToolExecuteBatchRequest } from '@/types/tools';
|
|
6
7
|
import type { AgentLogEvent } from '@/types/graph';
|
|
7
8
|
import { traceHostToolResults } from '@/langfuse';
|
|
@@ -24,11 +25,13 @@ export async function safeDispatchCustomEvent(
|
|
|
24
25
|
resolve: (
|
|
25
26
|
results: Parameters<ToolExecuteBatchRequest['resolve']>[0]
|
|
26
27
|
): void => {
|
|
28
|
+
const receivedAt = Date.now();
|
|
29
|
+
const stamped = results.map((result) => ({ ...result, received_at: receivedAt }));
|
|
27
30
|
void traceHostToolResults(request, results, config).then(
|
|
28
|
-
() => request.resolve(
|
|
31
|
+
() => request.resolve(stamped),
|
|
29
32
|
() => {
|
|
30
33
|
console.warn('Failed to record host tool execution metadata');
|
|
31
|
-
request.resolve(
|
|
34
|
+
request.resolve(stamped);
|
|
32
35
|
}
|
|
33
36
|
);
|
|
34
37
|
},
|
|
@@ -54,6 +57,22 @@ export async function safeDispatchCustomEvent(
|
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
59
|
|
|
60
|
+
/** Builds an argument-free snapshot once per handoff, not once per result. */
|
|
61
|
+
export function createToolCallsDispatchedEvent(
|
|
62
|
+
config: RunnableConfig,
|
|
63
|
+
calls: readonly Pick<
|
|
64
|
+
ToolExecuteBatchRequest['toolCalls'][number],
|
|
65
|
+
'id' | 'name' | 'stepId'
|
|
66
|
+
>[]
|
|
67
|
+
): ToolCallsDispatchedEvent {
|
|
68
|
+
const runId = config.configurable?.run_id;
|
|
69
|
+
return {
|
|
70
|
+
dispatched_at: Date.now(),
|
|
71
|
+
...(typeof runId === 'string' ? { runId } : {}),
|
|
72
|
+
toolCalls: calls.map(({ id, name, stepId }) => ({ id, name, stepId })),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
57
76
|
/**
|
|
58
77
|
* Fire-and-forget diagnostic log event.
|
|
59
78
|
* Debug-level logs are gated behind AGENT_DEBUG_LOGGING=true to avoid
|