@xynehq/jaf 0.1.19 → 0.1.21
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/README.md +7 -1
- package/dist/core/engine.d.ts.map +1 -1
- package/dist/core/engine.js +266 -44
- package/dist/core/engine.js.map +1 -1
- package/dist/core/types.d.ts +49 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +11 -0
- package/dist/core/types.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +19 -5
- package/dist/server/server.js.map +1 -1
- package/dist/server/types.d.ts +112 -14
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js +24 -11
- package/dist/server/types.js.map +1 -1
- package/dist/utils/constants.d.ts +2 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +4 -0
- package/dist/utils/constants.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -89,6 +89,7 @@ examples/
|
|
|
89
89
|
│ ├── index.ts # Demo entry point
|
|
90
90
|
│ ├── rag-agent.ts # RAG agent implementation
|
|
91
91
|
│ └── rag-tool.ts # RAG tool implementation
|
|
92
|
+
├── hooks/turn-end-review.ts # Demonstrates awaiting onTurnEnd reviews between turns
|
|
92
93
|
└── server-demo/ # Development server demo
|
|
93
94
|
└── index.ts # Server demo entry point
|
|
94
95
|
docs/ # Documentation
|
|
@@ -154,6 +155,10 @@ const config = {
|
|
|
154
155
|
modelProvider,
|
|
155
156
|
maxTurns: 10,
|
|
156
157
|
onEvent: (event) => console.log(event), // Real-time tracing
|
|
158
|
+
onTurnEnd: async ({ turn, lastAssistantMessage }) => {
|
|
159
|
+
console.log(`Turn ${turn} completed:`, lastAssistantMessage?.content);
|
|
160
|
+
// Run reviews, persist breadcrumbs, throttle next turn, etc.
|
|
161
|
+
},
|
|
157
162
|
};
|
|
158
163
|
|
|
159
164
|
const initialState = {
|
|
@@ -168,6 +173,7 @@ const initialState = {
|
|
|
168
173
|
const result = await run(initialState, config);
|
|
169
174
|
```
|
|
170
175
|
|
|
176
|
+
|
|
171
177
|
## 🔄 Function Composition
|
|
172
178
|
|
|
173
179
|
JAF emphasizes function composition to build complex behaviors from simple, reusable functions:
|
|
@@ -619,4 +625,4 @@ npm run typecheck # Type checking
|
|
|
619
625
|
|
|
620
626
|
---
|
|
621
627
|
|
|
622
|
-
**JAF** - Building the future of functional AI agent systems 🚀
|
|
628
|
+
**JAF** - Building the future of functional AI agent systems 🚀
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/core/engine.ts"],"names":[],"mappings":"AACA,OAAO,EACL,QAAQ,EACR,SAAS,EACT,SAAS,EAET,UAAU,
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/core/engine.ts"],"names":[],"mappings":"AACA,OAAO,EACL,QAAQ,EACR,SAAS,EACT,SAAS,EAET,UAAU,EASX,MAAM,YAAY,CAAC;AA8CpB,wBAAsB,GAAG,CAAC,GAAG,EAAE,GAAG,EAChC,YAAY,EAAE,QAAQ,CAAC,GAAG,CAAC,EAC3B,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,GACrB,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CA0EzB;AAsED;;;;;;;GAOG;AACH,wBAAuB,SAAS,CAAC,GAAG,EAAE,GAAG,EACvC,YAAY,EAAE,QAAQ,CAAC,GAAG,CAAC,EAC3B,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,EACtB,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GAAG,GAAG,GAAG,OAAO,CAAC,IAAI,GAAG,GAAG,CAAC,GAC3E,cAAc,CAAC,UAAU,EAAE,IAAI,EAAE,OAAO,CAAC,CAwC3C"}
|
package/dist/core/engine.js
CHANGED
|
@@ -1,7 +1,36 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getTextContent, InterruptionStatus, } from './types.js';
|
|
2
3
|
import { setToolRuntime } from './tool-runtime.js';
|
|
3
4
|
import { buildEffectiveGuardrails, executeInputGuardrailsParallel, executeInputGuardrailsSequential, executeOutputGuardrails } from './guardrails.js';
|
|
4
5
|
import { safeConsole } from '../utils/logger.js';
|
|
6
|
+
import { DEFAULT_CLARIFICATION_DESCRIPTION } from '../utils/constants.js';
|
|
7
|
+
/**
|
|
8
|
+
* Create the built-in clarification tool
|
|
9
|
+
*/
|
|
10
|
+
function createClarificationTool(config) {
|
|
11
|
+
const description = config.clarificationDescription || DEFAULT_CLARIFICATION_DESCRIPTION;
|
|
12
|
+
return {
|
|
13
|
+
schema: {
|
|
14
|
+
name: 'request_user_clarification',
|
|
15
|
+
description,
|
|
16
|
+
parameters: z.object({
|
|
17
|
+
question: z.string().describe('The clarifying question to ask the user'),
|
|
18
|
+
options: z.array(z.object({
|
|
19
|
+
id: z.string().describe('Unique identifier for this option'),
|
|
20
|
+
label: z.string().describe('Human-readable label shown to the user')
|
|
21
|
+
})).min(2).describe('clear and meaningful options that user can choose from (minimum 2 options)')
|
|
22
|
+
})
|
|
23
|
+
},
|
|
24
|
+
execute: async (args, _context) => {
|
|
25
|
+
const trigger = {
|
|
26
|
+
_clarification_trigger: true,
|
|
27
|
+
question: args.question,
|
|
28
|
+
options: args.options
|
|
29
|
+
};
|
|
30
|
+
return JSON.stringify(trigger);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
5
34
|
export async function run(initialState, config) {
|
|
6
35
|
try {
|
|
7
36
|
config.onEvent?.({
|
|
@@ -41,7 +70,12 @@ export async function run(initialState, config) {
|
|
|
41
70
|
}
|
|
42
71
|
config.onEvent?.({
|
|
43
72
|
type: 'run_end',
|
|
44
|
-
data: {
|
|
73
|
+
data: {
|
|
74
|
+
outcome: result.outcome,
|
|
75
|
+
finalState: result.finalState,
|
|
76
|
+
traceId: initialState.traceId,
|
|
77
|
+
runId: initialState.runId
|
|
78
|
+
}
|
|
45
79
|
});
|
|
46
80
|
return result;
|
|
47
81
|
}
|
|
@@ -58,7 +92,12 @@ export async function run(initialState, config) {
|
|
|
58
92
|
};
|
|
59
93
|
config.onEvent?.({
|
|
60
94
|
type: 'run_end',
|
|
61
|
-
data: {
|
|
95
|
+
data: {
|
|
96
|
+
outcome: errorResult.outcome,
|
|
97
|
+
finalState: errorResult.finalState,
|
|
98
|
+
traceId: initialState.traceId,
|
|
99
|
+
runId: initialState.runId
|
|
100
|
+
}
|
|
62
101
|
});
|
|
63
102
|
return errorResult;
|
|
64
103
|
}
|
|
@@ -108,6 +147,20 @@ function createAsyncEventStream() {
|
|
|
108
147
|
},
|
|
109
148
|
};
|
|
110
149
|
}
|
|
150
|
+
async function runTurnEndHooks(config, payload) {
|
|
151
|
+
config.onEvent?.({
|
|
152
|
+
type: 'turn_end',
|
|
153
|
+
data: { turn: payload.turn, agentName: payload.agentName }
|
|
154
|
+
});
|
|
155
|
+
if (config.onTurnEnd) {
|
|
156
|
+
await config.onTurnEnd({
|
|
157
|
+
turn: payload.turn,
|
|
158
|
+
agentName: payload.agentName,
|
|
159
|
+
state: payload.state,
|
|
160
|
+
lastAssistantMessage: payload.lastAssistantMessage
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
111
164
|
/**
|
|
112
165
|
* Stream run events as they happen via an async generator.
|
|
113
166
|
* Consumers can iterate events to build live UIs or forward via SSE.
|
|
@@ -238,6 +291,48 @@ async function runInternal(state, config) {
|
|
|
238
291
|
const resumed = await tryResumePendingToolCalls(state, config);
|
|
239
292
|
if (resumed)
|
|
240
293
|
return resumed;
|
|
294
|
+
// Check if we're resuming from a clarification
|
|
295
|
+
if (state.clarifications && state.clarifications.size > 0) {
|
|
296
|
+
const lastMessage = state.messages[state.messages.length - 1];
|
|
297
|
+
if (lastMessage?.role === 'tool') {
|
|
298
|
+
try {
|
|
299
|
+
const content = JSON.parse(getTextContent(lastMessage.content));
|
|
300
|
+
if (content.status === InterruptionStatus.AwaitingClarification) {
|
|
301
|
+
const clarificationId = content.clarification_id;
|
|
302
|
+
const selectedId = state.clarifications.get(clarificationId);
|
|
303
|
+
if (selectedId) {
|
|
304
|
+
safeConsole.log(`[JAF:ENGINE] Resuming with clarification: ${clarificationId}, selected option: ${selectedId}`);
|
|
305
|
+
// Find the selected option to include in the event
|
|
306
|
+
const updatedMessages = [...state.messages];
|
|
307
|
+
updatedMessages[updatedMessages.length - 1] = {
|
|
308
|
+
...lastMessage,
|
|
309
|
+
content: JSON.stringify({
|
|
310
|
+
status: InterruptionStatus.ClarificationProvided,
|
|
311
|
+
message: `User selected option: ${selectedId}`
|
|
312
|
+
})
|
|
313
|
+
};
|
|
314
|
+
config.onEvent?.({
|
|
315
|
+
type: 'clarification_provided',
|
|
316
|
+
data: {
|
|
317
|
+
clarificationId,
|
|
318
|
+
selectedId,
|
|
319
|
+
selectedOption: { id: selectedId, label: selectedId }
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
// Continue execution with updated messages
|
|
323
|
+
const stateWithClarification = {
|
|
324
|
+
...state,
|
|
325
|
+
messages: updatedMessages
|
|
326
|
+
};
|
|
327
|
+
return runInternal(stateWithClarification, config);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
catch (e) {
|
|
332
|
+
safeConsole.log(`[JAF:ENGINE] Error checking for clarification resume:`, e);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
241
336
|
const maxTurns = config.maxTurns ?? 50;
|
|
242
337
|
if (state.turnCount >= maxTurns) {
|
|
243
338
|
return {
|
|
@@ -295,26 +390,35 @@ async function runInternal(state, config) {
|
|
|
295
390
|
inputGuardrailsToRunLength: inputGuardrailsToRun.length,
|
|
296
391
|
hasAdvancedGuardrails
|
|
297
392
|
});
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
393
|
+
const effectiveTools = [
|
|
394
|
+
...(currentAgent.tools || [])
|
|
395
|
+
];
|
|
396
|
+
if (config.allowClarificationRequests) {
|
|
397
|
+
effectiveTools.push(createClarificationTool(config));
|
|
398
|
+
}
|
|
399
|
+
const effectiveAgent = {
|
|
400
|
+
...currentAgent,
|
|
401
|
+
tools: effectiveTools
|
|
402
|
+
};
|
|
403
|
+
safeConsole.log(`[JAF:ENGINE] Using agent: ${effectiveAgent.name}`);
|
|
404
|
+
if (effectiveTools) {
|
|
405
|
+
safeConsole.log(`[JAF:ENGINE] Available tools:`, effectiveTools.map(t => t.schema.name));
|
|
302
406
|
}
|
|
303
407
|
config.onEvent?.({
|
|
304
408
|
type: 'agent_processing',
|
|
305
409
|
data: {
|
|
306
|
-
agentName:
|
|
410
|
+
agentName: effectiveAgent.name,
|
|
307
411
|
traceId: state.traceId,
|
|
308
412
|
runId: state.runId,
|
|
309
413
|
turnCount: state.turnCount,
|
|
310
414
|
messageCount: state.messages.length,
|
|
311
|
-
toolsAvailable:
|
|
415
|
+
toolsAvailable: effectiveTools.map(t => ({
|
|
312
416
|
name: t.schema.name,
|
|
313
417
|
description: t.schema.description
|
|
314
|
-
}))
|
|
315
|
-
handoffsAvailable:
|
|
316
|
-
modelConfig:
|
|
317
|
-
hasOutputCodec: !!
|
|
418
|
+
})),
|
|
419
|
+
handoffsAvailable: effectiveAgent.handoffs || [],
|
|
420
|
+
modelConfig: effectiveAgent.modelConfig,
|
|
421
|
+
hasOutputCodec: !!effectiveAgent.outputCodec,
|
|
318
422
|
context: state.context,
|
|
319
423
|
currentState: {
|
|
320
424
|
messages: state.messages.map(m => ({
|
|
@@ -341,18 +445,18 @@ async function runInternal(state, config) {
|
|
|
341
445
|
const turnNumber = state.turnCount + 1;
|
|
342
446
|
config.onEvent?.({ type: 'turn_start', data: { turn: turnNumber, agentName: currentAgent.name } });
|
|
343
447
|
const llmCallData = {
|
|
344
|
-
agentName:
|
|
448
|
+
agentName: effectiveAgent.name,
|
|
345
449
|
model: model || 'unknown',
|
|
346
450
|
traceId: state.traceId,
|
|
347
451
|
runId: state.runId,
|
|
348
452
|
messages: state.messages,
|
|
349
|
-
tools:
|
|
453
|
+
tools: effectiveTools.map(tool => ({
|
|
350
454
|
name: tool.schema.name,
|
|
351
455
|
description: tool.schema.description,
|
|
352
456
|
parameters: tool.schema.parameters
|
|
353
457
|
})),
|
|
354
458
|
modelConfig: {
|
|
355
|
-
...
|
|
459
|
+
...effectiveAgent.modelConfig,
|
|
356
460
|
modelOverride: config.modelOverride
|
|
357
461
|
},
|
|
358
462
|
turnCount: state.turnCount,
|
|
@@ -373,6 +477,12 @@ async function runInternal(state, config) {
|
|
|
373
477
|
if (executionMode === 'sequential') {
|
|
374
478
|
const guardrailResult = await executeInputGuardrailsSequential(inputGuardrailsToRun, firstUserMessage, config);
|
|
375
479
|
if (!guardrailResult.isValid) {
|
|
480
|
+
await runTurnEndHooks(config, {
|
|
481
|
+
turn: turnNumber,
|
|
482
|
+
agentName: currentAgent.name,
|
|
483
|
+
state,
|
|
484
|
+
lastAssistantMessage: undefined
|
|
485
|
+
});
|
|
376
486
|
return {
|
|
377
487
|
finalState: state,
|
|
378
488
|
outcome: {
|
|
@@ -385,11 +495,11 @@ async function runInternal(state, config) {
|
|
|
385
495
|
};
|
|
386
496
|
}
|
|
387
497
|
safeConsole.log(`✅ All input guardrails passed. Starting LLM call.`);
|
|
388
|
-
llmResponse = await config.modelProvider.getCompletion(state,
|
|
498
|
+
llmResponse = await config.modelProvider.getCompletion(state, effectiveAgent, config);
|
|
389
499
|
}
|
|
390
500
|
else {
|
|
391
501
|
const guardrailPromise = executeInputGuardrailsParallel(inputGuardrailsToRun, firstUserMessage, config);
|
|
392
|
-
const llmPromise = config.modelProvider.getCompletion(state,
|
|
502
|
+
const llmPromise = config.modelProvider.getCompletion(state, effectiveAgent, config);
|
|
393
503
|
const [guardrailResult, llmResult] = await Promise.all([
|
|
394
504
|
guardrailPromise,
|
|
395
505
|
llmPromise
|
|
@@ -398,6 +508,12 @@ async function runInternal(state, config) {
|
|
|
398
508
|
if (!guardrailResult.isValid) {
|
|
399
509
|
safeConsole.log(`🚨 Input guardrail violation: ${guardrailResult.errorMessage}`);
|
|
400
510
|
safeConsole.log(`[JAF:GUARDRAILS] Discarding LLM response due to input guardrail violation`);
|
|
511
|
+
await runTurnEndHooks(config, {
|
|
512
|
+
turn: turnNumber,
|
|
513
|
+
agentName: currentAgent.name,
|
|
514
|
+
state,
|
|
515
|
+
lastAssistantMessage: undefined
|
|
516
|
+
});
|
|
401
517
|
return {
|
|
402
518
|
finalState: state,
|
|
403
519
|
outcome: {
|
|
@@ -422,6 +538,12 @@ async function runInternal(state, config) {
|
|
|
422
538
|
type: 'guardrail_violation',
|
|
423
539
|
data: { stage: 'input', reason: errorMessage }
|
|
424
540
|
});
|
|
541
|
+
await runTurnEndHooks(config, {
|
|
542
|
+
turn: turnNumber,
|
|
543
|
+
agentName: currentAgent.name,
|
|
544
|
+
state,
|
|
545
|
+
lastAssistantMessage: undefined
|
|
546
|
+
});
|
|
425
547
|
return {
|
|
426
548
|
finalState: state,
|
|
427
549
|
outcome: {
|
|
@@ -434,14 +556,14 @@ async function runInternal(state, config) {
|
|
|
434
556
|
};
|
|
435
557
|
}
|
|
436
558
|
}
|
|
437
|
-
llmResponse = await config.modelProvider.getCompletion(state,
|
|
559
|
+
llmResponse = await config.modelProvider.getCompletion(state, effectiveAgent, config);
|
|
438
560
|
}
|
|
439
561
|
}
|
|
440
562
|
else {
|
|
441
563
|
if (typeof config.modelProvider.getCompletionStream === 'function') {
|
|
442
564
|
try {
|
|
443
565
|
streamingUsed = true;
|
|
444
|
-
const stream = config.modelProvider.getCompletionStream(state,
|
|
566
|
+
const stream = config.modelProvider.getCompletionStream(state, effectiveAgent, config);
|
|
445
567
|
let aggregatedText = '';
|
|
446
568
|
const toolCalls = [];
|
|
447
569
|
for await (const chunk of stream) {
|
|
@@ -509,11 +631,11 @@ async function runInternal(state, config) {
|
|
|
509
631
|
catch (e) {
|
|
510
632
|
streamingUsed = false;
|
|
511
633
|
assistantEventStreamed = false;
|
|
512
|
-
llmResponse = await config.modelProvider.getCompletion(state,
|
|
634
|
+
llmResponse = await config.modelProvider.getCompletion(state, effectiveAgent, config);
|
|
513
635
|
}
|
|
514
636
|
}
|
|
515
637
|
else {
|
|
516
|
-
llmResponse = await config.modelProvider.getCompletion(state,
|
|
638
|
+
llmResponse = await config.modelProvider.getCompletion(state, effectiveAgent, config);
|
|
517
639
|
}
|
|
518
640
|
}
|
|
519
641
|
}
|
|
@@ -521,7 +643,7 @@ async function runInternal(state, config) {
|
|
|
521
643
|
if (typeof config.modelProvider.getCompletionStream === 'function') {
|
|
522
644
|
try {
|
|
523
645
|
streamingUsed = true;
|
|
524
|
-
const stream = config.modelProvider.getCompletionStream(state,
|
|
646
|
+
const stream = config.modelProvider.getCompletionStream(state, effectiveAgent, config);
|
|
525
647
|
let aggregatedText = '';
|
|
526
648
|
const toolCalls = [];
|
|
527
649
|
for await (const chunk of stream) {
|
|
@@ -589,11 +711,11 @@ async function runInternal(state, config) {
|
|
|
589
711
|
catch (e) {
|
|
590
712
|
streamingUsed = false;
|
|
591
713
|
assistantEventStreamed = false;
|
|
592
|
-
llmResponse = await config.modelProvider.getCompletion(state,
|
|
714
|
+
llmResponse = await config.modelProvider.getCompletion(state, effectiveAgent, config);
|
|
593
715
|
}
|
|
594
716
|
}
|
|
595
717
|
else {
|
|
596
|
-
llmResponse = await config.modelProvider.getCompletion(state,
|
|
718
|
+
llmResponse = await config.modelProvider.getCompletion(state, effectiveAgent, config);
|
|
597
719
|
}
|
|
598
720
|
}
|
|
599
721
|
const usage = llmResponse?.usage;
|
|
@@ -631,7 +753,12 @@ async function runInternal(state, config) {
|
|
|
631
753
|
}
|
|
632
754
|
catch { /* ignore */ }
|
|
633
755
|
if (!llmResponse.message) {
|
|
634
|
-
config
|
|
756
|
+
await runTurnEndHooks(config, {
|
|
757
|
+
turn: turnNumber,
|
|
758
|
+
agentName: currentAgent.name,
|
|
759
|
+
state,
|
|
760
|
+
lastAssistantMessage: undefined
|
|
761
|
+
});
|
|
635
762
|
return {
|
|
636
763
|
finalState: state,
|
|
637
764
|
outcome: {
|
|
@@ -668,7 +795,7 @@ async function runInternal(state, config) {
|
|
|
668
795
|
config.onEvent?.({ type: 'tool_requests', data: { toolCalls: requests } });
|
|
669
796
|
}
|
|
670
797
|
catch { /* ignore */ }
|
|
671
|
-
const toolResults = await executeToolCalls(llmResponse.message.tool_calls,
|
|
798
|
+
const toolResults = await executeToolCalls(llmResponse.message.tool_calls, effectiveAgent, state, config);
|
|
672
799
|
const interruptions = toolResults
|
|
673
800
|
.map(r => r.interruption)
|
|
674
801
|
.filter((interruption) => interruption !== undefined);
|
|
@@ -676,6 +803,7 @@ async function runInternal(state, config) {
|
|
|
676
803
|
const completedToolResults = toolResults.filter(r => !r.interruption);
|
|
677
804
|
const approvalRequiredResults = toolResults.filter(r => r.interruption);
|
|
678
805
|
const updatedApprovals = new Map(state.approvals ?? []);
|
|
806
|
+
const updatedClarifications = new Map(state.clarifications ?? []);
|
|
679
807
|
for (const interruption of interruptions) {
|
|
680
808
|
if (interruption.type === 'tool_approval') {
|
|
681
809
|
updatedApprovals.set(interruption.toolCall.id, {
|
|
@@ -684,12 +812,26 @@ async function runInternal(state, config) {
|
|
|
684
812
|
additionalContext: { status: 'pending', timestamp: new Date().toISOString() }
|
|
685
813
|
});
|
|
686
814
|
}
|
|
815
|
+
else if (interruption.type === 'clarification_required') {
|
|
816
|
+
// Emit clarification requested event
|
|
817
|
+
config.onEvent?.({
|
|
818
|
+
type: 'clarification_requested',
|
|
819
|
+
data: {
|
|
820
|
+
clarificationId: interruption.clarificationId,
|
|
821
|
+
question: interruption.question,
|
|
822
|
+
options: interruption.options,
|
|
823
|
+
context: interruption.context
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
safeConsole.log(`[JAF:ENGINE] Clarification requested: ${interruption.question}`);
|
|
827
|
+
}
|
|
687
828
|
}
|
|
688
829
|
const interruptedState = {
|
|
689
830
|
...state,
|
|
690
831
|
messages: [...newMessages, ...completedToolResults.map(r => r.message)],
|
|
691
832
|
turnCount: updatedTurnCount,
|
|
692
833
|
approvals: updatedApprovals,
|
|
834
|
+
clarifications: updatedClarifications,
|
|
693
835
|
};
|
|
694
836
|
if (config.memory?.autoStore && config.conversationId) {
|
|
695
837
|
safeConsole.log(`[JAF:ENGINE] Storing conversation state due to interruption for ${config.conversationId}`);
|
|
@@ -699,6 +841,12 @@ async function runInternal(state, config) {
|
|
|
699
841
|
};
|
|
700
842
|
await storeConversationHistory(stateForStorage, config);
|
|
701
843
|
}
|
|
844
|
+
await runTurnEndHooks(config, {
|
|
845
|
+
turn: turnNumber,
|
|
846
|
+
agentName: currentAgent.name,
|
|
847
|
+
state: interruptedState,
|
|
848
|
+
lastAssistantMessage: assistantMessage
|
|
849
|
+
});
|
|
702
850
|
return {
|
|
703
851
|
finalState: interruptedState,
|
|
704
852
|
outcome: {
|
|
@@ -707,7 +855,7 @@ async function runInternal(state, config) {
|
|
|
707
855
|
},
|
|
708
856
|
};
|
|
709
857
|
}
|
|
710
|
-
safeConsole.log(`[JAF:ENGINE] Tool execution completed. Results count:`, toolResults.length);
|
|
858
|
+
// safeConsole.log(`[JAF:ENGINE] Tool execution completed. Results count:`, toolResults.length);
|
|
711
859
|
config.onEvent?.({
|
|
712
860
|
type: 'tool_results_to_llm',
|
|
713
861
|
data: { results: toolResults.map(r => r.message) }
|
|
@@ -721,8 +869,15 @@ async function runInternal(state, config) {
|
|
|
721
869
|
type: 'handoff_denied',
|
|
722
870
|
data: { from: currentAgent.name, to: targetAgent, reason: `Agent ${currentAgent.name} cannot handoff to ${targetAgent}` }
|
|
723
871
|
});
|
|
872
|
+
const failureState = { ...state, messages: newMessages, turnCount: updatedTurnCount };
|
|
873
|
+
await runTurnEndHooks(config, {
|
|
874
|
+
turn: turnNumber,
|
|
875
|
+
agentName: currentAgent.name,
|
|
876
|
+
state: failureState,
|
|
877
|
+
lastAssistantMessage: assistantMessage
|
|
878
|
+
});
|
|
724
879
|
return {
|
|
725
|
-
finalState:
|
|
880
|
+
finalState: failureState,
|
|
726
881
|
outcome: {
|
|
727
882
|
status: 'error',
|
|
728
883
|
error: {
|
|
@@ -759,7 +914,12 @@ async function runInternal(state, config) {
|
|
|
759
914
|
turnCount: updatedTurnCount,
|
|
760
915
|
approvals: state.approvals ?? new Map(),
|
|
761
916
|
};
|
|
762
|
-
config
|
|
917
|
+
await runTurnEndHooks(config, {
|
|
918
|
+
turn: turnNumber,
|
|
919
|
+
agentName: currentAgent.name,
|
|
920
|
+
state: nextState,
|
|
921
|
+
lastAssistantMessage: assistantMessage
|
|
922
|
+
});
|
|
763
923
|
return runInternal(nextState, config);
|
|
764
924
|
}
|
|
765
925
|
}
|
|
@@ -785,7 +945,12 @@ async function runInternal(state, config) {
|
|
|
785
945
|
turnCount: updatedTurnCount,
|
|
786
946
|
approvals: state.approvals ?? new Map(),
|
|
787
947
|
};
|
|
788
|
-
config
|
|
948
|
+
await runTurnEndHooks(config, {
|
|
949
|
+
turn: turnNumber,
|
|
950
|
+
agentName: currentAgent.name,
|
|
951
|
+
state: nextState,
|
|
952
|
+
lastAssistantMessage: assistantMessage
|
|
953
|
+
});
|
|
789
954
|
return runInternal(nextState, config);
|
|
790
955
|
}
|
|
791
956
|
if (llmResponse.message.content) {
|
|
@@ -793,7 +958,12 @@ async function runInternal(state, config) {
|
|
|
793
958
|
const parseResult = currentAgent.outputCodec.safeParse(tryParseJSON(llmResponse.message.content));
|
|
794
959
|
if (!parseResult.success) {
|
|
795
960
|
config.onEvent?.({ type: 'decode_error', data: { errors: parseResult.error.issues } });
|
|
796
|
-
config
|
|
961
|
+
await runTurnEndHooks(config, {
|
|
962
|
+
turn: turnNumber,
|
|
963
|
+
agentName: currentAgent.name,
|
|
964
|
+
state: { ...state, messages: newMessages, turnCount: updatedTurnCount },
|
|
965
|
+
lastAssistantMessage: assistantMessage
|
|
966
|
+
});
|
|
797
967
|
return {
|
|
798
968
|
finalState: { ...state, messages: newMessages, turnCount: updatedTurnCount },
|
|
799
969
|
outcome: {
|
|
@@ -825,7 +995,12 @@ async function runInternal(state, config) {
|
|
|
825
995
|
}
|
|
826
996
|
}
|
|
827
997
|
if (!outputGuardrailResult.isValid) {
|
|
828
|
-
config
|
|
998
|
+
await runTurnEndHooks(config, {
|
|
999
|
+
turn: turnNumber,
|
|
1000
|
+
agentName: currentAgent.name,
|
|
1001
|
+
state: { ...state, messages: newMessages, turnCount: updatedTurnCount },
|
|
1002
|
+
lastAssistantMessage: assistantMessage
|
|
1003
|
+
});
|
|
829
1004
|
return {
|
|
830
1005
|
finalState: { ...state, messages: newMessages, turnCount: updatedTurnCount },
|
|
831
1006
|
outcome: {
|
|
@@ -839,7 +1014,12 @@ async function runInternal(state, config) {
|
|
|
839
1014
|
}
|
|
840
1015
|
config.onEvent?.({ type: 'final_output', data: { output: parseResult.data } });
|
|
841
1016
|
// End of turn
|
|
842
|
-
config
|
|
1017
|
+
await runTurnEndHooks(config, {
|
|
1018
|
+
turn: turnNumber,
|
|
1019
|
+
agentName: currentAgent.name,
|
|
1020
|
+
state: { ...state, messages: newMessages, turnCount: updatedTurnCount },
|
|
1021
|
+
lastAssistantMessage: assistantMessage
|
|
1022
|
+
});
|
|
843
1023
|
return {
|
|
844
1024
|
finalState: { ...state, messages: newMessages, turnCount: updatedTurnCount },
|
|
845
1025
|
outcome: {
|
|
@@ -869,7 +1049,12 @@ async function runInternal(state, config) {
|
|
|
869
1049
|
}
|
|
870
1050
|
}
|
|
871
1051
|
if (!outputGuardrailResult.isValid) {
|
|
872
|
-
config
|
|
1052
|
+
await runTurnEndHooks(config, {
|
|
1053
|
+
turn: turnNumber,
|
|
1054
|
+
agentName: currentAgent.name,
|
|
1055
|
+
state: { ...state, messages: newMessages, turnCount: updatedTurnCount },
|
|
1056
|
+
lastAssistantMessage: assistantMessage
|
|
1057
|
+
});
|
|
873
1058
|
return {
|
|
874
1059
|
finalState: { ...state, messages: newMessages, turnCount: updatedTurnCount },
|
|
875
1060
|
outcome: {
|
|
@@ -883,7 +1068,12 @@ async function runInternal(state, config) {
|
|
|
883
1068
|
}
|
|
884
1069
|
config.onEvent?.({ type: 'final_output', data: { output: llmResponse.message.content } });
|
|
885
1070
|
// End of turn
|
|
886
|
-
config
|
|
1071
|
+
await runTurnEndHooks(config, {
|
|
1072
|
+
turn: turnNumber,
|
|
1073
|
+
agentName: currentAgent.name,
|
|
1074
|
+
state: { ...state, messages: newMessages, turnCount: updatedTurnCount },
|
|
1075
|
+
lastAssistantMessage: assistantMessage
|
|
1076
|
+
});
|
|
887
1077
|
return {
|
|
888
1078
|
finalState: { ...state, messages: newMessages, turnCount: updatedTurnCount },
|
|
889
1079
|
outcome: {
|
|
@@ -893,7 +1083,12 @@ async function runInternal(state, config) {
|
|
|
893
1083
|
};
|
|
894
1084
|
}
|
|
895
1085
|
}
|
|
896
|
-
config
|
|
1086
|
+
await runTurnEndHooks(config, {
|
|
1087
|
+
turn: turnNumber,
|
|
1088
|
+
agentName: currentAgent.name,
|
|
1089
|
+
state: { ...state, messages: newMessages, turnCount: updatedTurnCount },
|
|
1090
|
+
lastAssistantMessage: assistantMessage
|
|
1091
|
+
});
|
|
897
1092
|
safeConsole.error(`[JAF:ENGINE] No tool calls or content returned by model. LLMResponse: `, llmResponse);
|
|
898
1093
|
return {
|
|
899
1094
|
finalState: { ...state, messages: newMessages, turnCount: updatedTurnCount },
|
|
@@ -938,14 +1133,10 @@ async function executeToolCalls(toolCalls, agent, state, config) {
|
|
|
938
1133
|
});
|
|
939
1134
|
// If event handler returns a value, use it to override the args
|
|
940
1135
|
if (beforeEventResponse !== undefined && beforeEventResponse !== null) {
|
|
941
|
-
console.log(`[JAF:ENGINE] Tool args modified by before_tool_execution event handler for ${toolCall.function.name}`);
|
|
942
|
-
console.log(`[JAF:ENGINE] Original args:`, rawArgs);
|
|
943
|
-
console.log(`[JAF:ENGINE] Modified args:`, beforeEventResponse);
|
|
944
1136
|
rawArgs = beforeEventResponse;
|
|
945
1137
|
}
|
|
946
1138
|
}
|
|
947
1139
|
catch (eventError) {
|
|
948
|
-
console.error(`[JAF:ENGINE] Error in before_tool_execution event handler:`, eventError);
|
|
949
1140
|
// Continue with original args if event handler fails
|
|
950
1141
|
}
|
|
951
1142
|
}
|
|
@@ -1079,6 +1270,38 @@ async function executeToolCalls(toolCalls, agent, state, config) {
|
|
|
1079
1270
|
? { ...state.context, ...additionalContext }
|
|
1080
1271
|
: state.context;
|
|
1081
1272
|
let toolResult = await tool.execute(parseResult.data, contextWithAdditional);
|
|
1273
|
+
// Check if this is a clarification request
|
|
1274
|
+
// The clarification tool returns a JSON string containing the trigger marker
|
|
1275
|
+
if (typeof toolResult === 'string') {
|
|
1276
|
+
try {
|
|
1277
|
+
const parsed = JSON.parse(toolResult);
|
|
1278
|
+
if (parsed && typeof parsed === 'object' && '_clarification_trigger' in parsed && parsed._clarification_trigger === true) {
|
|
1279
|
+
const clarificationId = `clarify_${toolCall.id}`;
|
|
1280
|
+
const trigger = parsed;
|
|
1281
|
+
return {
|
|
1282
|
+
interruption: {
|
|
1283
|
+
type: 'clarification_required',
|
|
1284
|
+
clarificationId,
|
|
1285
|
+
question: trigger.question,
|
|
1286
|
+
options: trigger.options,
|
|
1287
|
+
context: trigger.context
|
|
1288
|
+
},
|
|
1289
|
+
message: {
|
|
1290
|
+
role: 'tool',
|
|
1291
|
+
content: JSON.stringify({
|
|
1292
|
+
status: InterruptionStatus.AwaitingClarification,
|
|
1293
|
+
clarification_id: clarificationId,
|
|
1294
|
+
message: 'Waiting for user to provide clarification'
|
|
1295
|
+
}),
|
|
1296
|
+
tool_call_id: toolCall.id
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
catch {
|
|
1302
|
+
// Not a clarification trigger, continue with normal processing
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1082
1305
|
// Apply onAfterToolExecution callback if configured
|
|
1083
1306
|
if (config.onAfterToolExecution) {
|
|
1084
1307
|
try {
|
|
@@ -1104,14 +1327,13 @@ async function executeToolCalls(toolCalls, agent, state, config) {
|
|
|
1104
1327
|
let toolResultObj = null;
|
|
1105
1328
|
if (typeof toolResult === 'string') {
|
|
1106
1329
|
resultString = toolResult;
|
|
1107
|
-
safeConsole.log(`[JAF:ENGINE] Tool ${toolCall.function.name}
|
|
1330
|
+
safeConsole.log(`[JAF:ENGINE] Tool ${toolCall.function.name}`);
|
|
1108
1331
|
}
|
|
1109
1332
|
else {
|
|
1110
1333
|
toolResultObj = toolResult;
|
|
1111
1334
|
const { toolResultToString } = await import('./tool-results');
|
|
1112
1335
|
resultString = toolResultToString(toolResult);
|
|
1113
|
-
safeConsole.log(`[JAF:ENGINE] Tool ${toolCall.function.name}
|
|
1114
|
-
safeConsole.log(`[JAF:ENGINE] Converted to string:`, resultString);
|
|
1336
|
+
safeConsole.log(`[JAF:ENGINE] Tool ${toolCall.function.name} `);
|
|
1115
1337
|
}
|
|
1116
1338
|
config.onEvent?.({
|
|
1117
1339
|
type: 'tool_call_end',
|