@agentforge/patterns 0.15.9 → 0.15.10

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 (3) hide show
  1. package/dist/index.cjs +106 -67
  2. package/dist/index.js +112 -68
  3. package/package.json +3 -3
package/dist/index.cjs CHANGED
@@ -343,54 +343,101 @@ function handleNodeError(error, context, verbose = false) {
343
343
  var reasoningLogger = createPatternLogger("agentforge:patterns:react:reasoning");
344
344
  var actionLogger = createPatternLogger("agentforge:patterns:react:action");
345
345
  var observationLogger = createPatternLogger("agentforge:patterns:react:observation");
346
+ function normalizeConversationMessage(message) {
347
+ switch (message.role) {
348
+ case "user":
349
+ return new import_messages.HumanMessage(message.content);
350
+ case "assistant":
351
+ return new import_messages.AIMessage(message.content);
352
+ case "system":
353
+ return new import_messages.SystemMessage(message.content);
354
+ case "tool":
355
+ if (!message.tool_call_id) {
356
+ reasoningLogger.warn(
357
+ "Tool message missing tool_call_id; falling back to human message",
358
+ message.name ? { name: message.name } : void 0
359
+ );
360
+ return new import_messages.HumanMessage(message.content);
361
+ }
362
+ return new import_messages.ToolMessage({
363
+ content: message.content,
364
+ tool_call_id: message.tool_call_id,
365
+ name: message.name
366
+ });
367
+ default:
368
+ return new import_messages.HumanMessage(message.content);
369
+ }
370
+ }
371
+ function buildReasoningMessages(systemPrompt, stateMessages, scratchpad) {
372
+ const messages = [
373
+ new import_messages.SystemMessage(systemPrompt),
374
+ ...stateMessages.map(normalizeConversationMessage)
375
+ ];
376
+ if (scratchpad.length > 0) {
377
+ messages.push(new import_messages.SystemMessage(`Previous steps:
378
+ ${formatScratchpad(scratchpad)}`));
379
+ }
380
+ return messages;
381
+ }
382
+ function extractToolCalls(response) {
383
+ if (!response.tool_calls || response.tool_calls.length === 0) {
384
+ return [];
385
+ }
386
+ return response.tool_calls.map((toolCall) => ({
387
+ id: toolCall.id || `call_${Date.now()}_${Math.random()}`,
388
+ name: toolCall.name,
389
+ arguments: toolCall.args ?? {},
390
+ timestamp: Date.now()
391
+ }));
392
+ }
393
+ function formatObservationContent(observation) {
394
+ if (observation.error) {
395
+ return `Error: ${observation.error}`;
396
+ }
397
+ return stringifyObservationResult(observation.result, 2);
398
+ }
399
+ function formatActionSummary(actions) {
400
+ return actions.map((action) => `${action.name}(${JSON.stringify(action.arguments)})`).join(", ");
401
+ }
402
+ function formatObservationSummary(observations) {
403
+ return observations.map((observation) => {
404
+ if (observation.error) {
405
+ return `Error: ${observation.error}`;
406
+ }
407
+ return stringifyObservationResult(observation.result);
408
+ }).join("; ");
409
+ }
410
+ function stringifyObservationResult(result, space) {
411
+ if (typeof result === "string") {
412
+ return result;
413
+ }
414
+ const stringified = JSON.stringify(result, null, space);
415
+ return stringified ?? String(result);
416
+ }
417
+ function getLatestThought(thoughts) {
418
+ return thoughts[thoughts.length - 1]?.content ?? "";
419
+ }
420
+ function debugIfVerbose(logger4, verbose, message, data) {
421
+ if (verbose) {
422
+ logger4.debug(message, data);
423
+ }
424
+ }
346
425
  function createReasoningNode(llm, tools, systemPrompt, maxIterations, verbose = false) {
347
426
  const langchainTools = (0, import_core3.toLangChainTools)(tools);
348
427
  const llmWithTools = llm.bindTools ? llm.bindTools(langchainTools) : llm;
349
428
  return async (state) => {
350
429
  const currentIteration = state.iteration || 0;
351
430
  const startTime = Date.now();
352
- reasoningLogger.debug("Reasoning iteration started", {
431
+ debugIfVerbose(reasoningLogger, verbose, "Reasoning iteration started", {
353
432
  iteration: currentIteration + 1,
354
433
  maxIterations,
355
- observationCount: state.observations?.length || 0,
356
- hasActions: !!state.actions?.length
434
+ observationCount: state.observations.length,
435
+ hasActions: state.actions.length > 0
357
436
  });
358
- const stateMessages = state.messages || [];
359
- const messages = [
360
- new import_messages.SystemMessage(systemPrompt),
361
- ...stateMessages.map((msg) => {
362
- if (msg.role === "user") return new import_messages.HumanMessage(msg.content);
363
- if (msg.role === "assistant") return new import_messages.AIMessage(msg.content);
364
- if (msg.role === "system") return new import_messages.SystemMessage(msg.content);
365
- if (msg.role === "tool") {
366
- return new import_messages.ToolMessage({
367
- content: msg.content,
368
- tool_call_id: msg.tool_call_id,
369
- name: msg.name
370
- });
371
- }
372
- return new import_messages.HumanMessage(msg.content);
373
- })
374
- ];
375
- const scratchpad = state.scratchpad || [];
376
- if (scratchpad.length > 0) {
377
- const scratchpadText = formatScratchpad(scratchpad);
378
- messages.push(new import_messages.SystemMessage(`Previous steps:
379
- ${scratchpadText}`));
380
- }
437
+ const messages = buildReasoningMessages(systemPrompt, state.messages, state.scratchpad);
381
438
  const response = await llmWithTools.invoke(messages);
382
439
  const thought = typeof response.content === "string" ? response.content : "";
383
- const toolCalls = [];
384
- if (response.tool_calls && response.tool_calls.length > 0) {
385
- for (const toolCall of response.tool_calls) {
386
- toolCalls.push({
387
- id: toolCall.id || `call_${Date.now()}_${Math.random()}`,
388
- name: toolCall.name,
389
- arguments: toolCall.args || {},
390
- timestamp: Date.now()
391
- });
392
- }
393
- }
440
+ const toolCalls = extractToolCalls(response);
394
441
  const shouldContinue = toolCalls.length > 0 && currentIteration + 1 < maxIterations;
395
442
  reasoningLogger.info("Reasoning complete", {
396
443
  iteration: currentIteration + 1,
@@ -415,11 +462,11 @@ ${scratchpadText}`));
415
462
  function createActionNode(tools, verbose = false, enableDeduplication = true) {
416
463
  const toolMap = new Map(tools.map((tool) => [tool.metadata.name, tool]));
417
464
  return async (state) => {
418
- const actions = state.actions || [];
419
- const allObservations = state.observations || [];
465
+ const actions = state.actions;
466
+ const allObservations = state.observations;
420
467
  const iteration = state.iteration || 0;
421
468
  const startTime = Date.now();
422
- actionLogger.debug("Action node started", {
469
+ debugIfVerbose(actionLogger, verbose, "Action node started", {
423
470
  actionCount: actions.length,
424
471
  iteration,
425
472
  cacheEnabled: enableDeduplication
@@ -438,7 +485,7 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
438
485
  }
439
486
  }
440
487
  if (cacheSize > 0) {
441
- actionLogger.debug("Deduplication cache built", {
488
+ debugIfVerbose(actionLogger, verbose, "Deduplication cache built", {
442
489
  cacheSize,
443
490
  totalObservations: allObservations.length
444
491
  });
@@ -449,7 +496,7 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
449
496
  for (const action of recentActions) {
450
497
  const existingObservation = allObservations.find((obs) => obs.toolCallId === action.id);
451
498
  if (existingObservation) {
452
- actionLogger.debug("Skipping already-processed action", {
499
+ debugIfVerbose(actionLogger, verbose, "Skipping already-processed action", {
453
500
  toolName: action.name,
454
501
  toolCallId: action.id,
455
502
  iteration
@@ -492,7 +539,7 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
492
539
  const result = await tool.invoke(action.arguments);
493
540
  const executionTime = Date.now() - startTime2;
494
541
  toolsExecuted++;
495
- actionLogger.debug("Tool executed successfully", {
542
+ debugIfVerbose(actionLogger, verbose, "Tool executed successfully", {
496
543
  toolName: action.name,
497
544
  executionTime,
498
545
  iteration
@@ -508,7 +555,7 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
508
555
  executionCache.set(cacheKey, observation);
509
556
  }
510
557
  } catch (error) {
511
- const errorMessage = handleNodeError(error, `action:${action.name}`, false);
558
+ const errorMessage = handleNodeError(error, `action:${action.name}`, verbose);
512
559
  actionLogger.error("Tool execution failed", {
513
560
  toolName: action.name,
514
561
  error: errorMessage,
@@ -537,39 +584,31 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
537
584
  }
538
585
  function createObservationNode(verbose = false, returnIntermediateSteps = false) {
539
586
  return async (state) => {
540
- const observations = state.observations || [];
541
- const thoughts = state.thoughts || [];
542
- const actions = state.actions || [];
587
+ const observations = state.observations;
588
+ const thoughts = state.thoughts;
589
+ const actions = state.actions;
543
590
  const iteration = state.iteration || 0;
544
- observationLogger.debug("Processing observations", {
591
+ debugIfVerbose(observationLogger, verbose, "Processing observations", {
545
592
  observationCount: observations.length,
546
593
  iteration
547
594
  });
548
595
  const recentObservations = observations.slice(-10);
549
596
  const latestActions = actions.slice(-10);
550
- const observationMessages = recentObservations.map((obs) => {
551
- const content = obs.error ? `Error: ${obs.error}` : typeof obs.result === "string" ? obs.result : JSON.stringify(obs.result, null, 2);
552
- return {
553
- role: "tool",
554
- content,
555
- name: latestActions.find((a) => a.id === obs.toolCallId)?.name,
556
- tool_call_id: obs.toolCallId
557
- // Include tool_call_id for proper ToolMessage construction
558
- };
559
- });
597
+ const actionNamesById = new Map(latestActions.map((action) => [action.id, action.name]));
598
+ const observationMessages = recentObservations.map((observation) => ({
599
+ role: "tool",
600
+ content: formatObservationContent(observation),
601
+ name: actionNamesById.get(observation.toolCallId),
602
+ tool_call_id: observation.toolCallId
603
+ }));
560
604
  const scratchpadEntries = returnIntermediateSteps ? [{
561
605
  step: state.iteration,
562
- thought: thoughts[thoughts.length - 1]?.content || "",
563
- action: latestActions.map((a) => `${a.name}(${JSON.stringify(a.arguments)})`).join(", "),
564
- observation: recentObservations.map((obs) => {
565
- if (obs.error) {
566
- return `Error: ${obs.error}`;
567
- }
568
- return typeof obs.result === "string" ? obs.result : JSON.stringify(obs.result);
569
- }).join("; "),
606
+ thought: getLatestThought(thoughts),
607
+ action: formatActionSummary(latestActions),
608
+ observation: formatObservationSummary(recentObservations),
570
609
  timestamp: Date.now()
571
610
  }] : [];
572
- observationLogger.debug("Observation node complete", {
611
+ debugIfVerbose(observationLogger, verbose, "Observation node complete", {
573
612
  iteration,
574
613
  scratchpadUpdated: returnIntermediateSteps,
575
614
  messageCount: observationMessages.length
package/dist/index.js CHANGED
@@ -171,7 +171,12 @@ function formatScratchpad(scratchpad) {
171
171
  }
172
172
 
173
173
  // src/react/nodes.ts
174
- import { HumanMessage, AIMessage, SystemMessage, ToolMessage } from "@langchain/core/messages";
174
+ import {
175
+ HumanMessage,
176
+ AIMessage,
177
+ SystemMessage,
178
+ ToolMessage
179
+ } from "@langchain/core/messages";
175
180
  import { toLangChainTools } from "@agentforge/core";
176
181
 
177
182
  // src/shared/deduplication.ts
@@ -240,54 +245,101 @@ function handleNodeError(error, context, verbose = false) {
240
245
  var reasoningLogger = createPatternLogger("agentforge:patterns:react:reasoning");
241
246
  var actionLogger = createPatternLogger("agentforge:patterns:react:action");
242
247
  var observationLogger = createPatternLogger("agentforge:patterns:react:observation");
248
+ function normalizeConversationMessage(message) {
249
+ switch (message.role) {
250
+ case "user":
251
+ return new HumanMessage(message.content);
252
+ case "assistant":
253
+ return new AIMessage(message.content);
254
+ case "system":
255
+ return new SystemMessage(message.content);
256
+ case "tool":
257
+ if (!message.tool_call_id) {
258
+ reasoningLogger.warn(
259
+ "Tool message missing tool_call_id; falling back to human message",
260
+ message.name ? { name: message.name } : void 0
261
+ );
262
+ return new HumanMessage(message.content);
263
+ }
264
+ return new ToolMessage({
265
+ content: message.content,
266
+ tool_call_id: message.tool_call_id,
267
+ name: message.name
268
+ });
269
+ default:
270
+ return new HumanMessage(message.content);
271
+ }
272
+ }
273
+ function buildReasoningMessages(systemPrompt, stateMessages, scratchpad) {
274
+ const messages = [
275
+ new SystemMessage(systemPrompt),
276
+ ...stateMessages.map(normalizeConversationMessage)
277
+ ];
278
+ if (scratchpad.length > 0) {
279
+ messages.push(new SystemMessage(`Previous steps:
280
+ ${formatScratchpad(scratchpad)}`));
281
+ }
282
+ return messages;
283
+ }
284
+ function extractToolCalls(response) {
285
+ if (!response.tool_calls || response.tool_calls.length === 0) {
286
+ return [];
287
+ }
288
+ return response.tool_calls.map((toolCall) => ({
289
+ id: toolCall.id || `call_${Date.now()}_${Math.random()}`,
290
+ name: toolCall.name,
291
+ arguments: toolCall.args ?? {},
292
+ timestamp: Date.now()
293
+ }));
294
+ }
295
+ function formatObservationContent(observation) {
296
+ if (observation.error) {
297
+ return `Error: ${observation.error}`;
298
+ }
299
+ return stringifyObservationResult(observation.result, 2);
300
+ }
301
+ function formatActionSummary(actions) {
302
+ return actions.map((action) => `${action.name}(${JSON.stringify(action.arguments)})`).join(", ");
303
+ }
304
+ function formatObservationSummary(observations) {
305
+ return observations.map((observation) => {
306
+ if (observation.error) {
307
+ return `Error: ${observation.error}`;
308
+ }
309
+ return stringifyObservationResult(observation.result);
310
+ }).join("; ");
311
+ }
312
+ function stringifyObservationResult(result, space) {
313
+ if (typeof result === "string") {
314
+ return result;
315
+ }
316
+ const stringified = JSON.stringify(result, null, space);
317
+ return stringified ?? String(result);
318
+ }
319
+ function getLatestThought(thoughts) {
320
+ return thoughts[thoughts.length - 1]?.content ?? "";
321
+ }
322
+ function debugIfVerbose(logger4, verbose, message, data) {
323
+ if (verbose) {
324
+ logger4.debug(message, data);
325
+ }
326
+ }
243
327
  function createReasoningNode(llm, tools, systemPrompt, maxIterations, verbose = false) {
244
328
  const langchainTools = toLangChainTools(tools);
245
329
  const llmWithTools = llm.bindTools ? llm.bindTools(langchainTools) : llm;
246
330
  return async (state) => {
247
331
  const currentIteration = state.iteration || 0;
248
332
  const startTime = Date.now();
249
- reasoningLogger.debug("Reasoning iteration started", {
333
+ debugIfVerbose(reasoningLogger, verbose, "Reasoning iteration started", {
250
334
  iteration: currentIteration + 1,
251
335
  maxIterations,
252
- observationCount: state.observations?.length || 0,
253
- hasActions: !!state.actions?.length
336
+ observationCount: state.observations.length,
337
+ hasActions: state.actions.length > 0
254
338
  });
255
- const stateMessages = state.messages || [];
256
- const messages = [
257
- new SystemMessage(systemPrompt),
258
- ...stateMessages.map((msg) => {
259
- if (msg.role === "user") return new HumanMessage(msg.content);
260
- if (msg.role === "assistant") return new AIMessage(msg.content);
261
- if (msg.role === "system") return new SystemMessage(msg.content);
262
- if (msg.role === "tool") {
263
- return new ToolMessage({
264
- content: msg.content,
265
- tool_call_id: msg.tool_call_id,
266
- name: msg.name
267
- });
268
- }
269
- return new HumanMessage(msg.content);
270
- })
271
- ];
272
- const scratchpad = state.scratchpad || [];
273
- if (scratchpad.length > 0) {
274
- const scratchpadText = formatScratchpad(scratchpad);
275
- messages.push(new SystemMessage(`Previous steps:
276
- ${scratchpadText}`));
277
- }
339
+ const messages = buildReasoningMessages(systemPrompt, state.messages, state.scratchpad);
278
340
  const response = await llmWithTools.invoke(messages);
279
341
  const thought = typeof response.content === "string" ? response.content : "";
280
- const toolCalls = [];
281
- if (response.tool_calls && response.tool_calls.length > 0) {
282
- for (const toolCall of response.tool_calls) {
283
- toolCalls.push({
284
- id: toolCall.id || `call_${Date.now()}_${Math.random()}`,
285
- name: toolCall.name,
286
- arguments: toolCall.args || {},
287
- timestamp: Date.now()
288
- });
289
- }
290
- }
342
+ const toolCalls = extractToolCalls(response);
291
343
  const shouldContinue = toolCalls.length > 0 && currentIteration + 1 < maxIterations;
292
344
  reasoningLogger.info("Reasoning complete", {
293
345
  iteration: currentIteration + 1,
@@ -312,11 +364,11 @@ ${scratchpadText}`));
312
364
  function createActionNode(tools, verbose = false, enableDeduplication = true) {
313
365
  const toolMap = new Map(tools.map((tool) => [tool.metadata.name, tool]));
314
366
  return async (state) => {
315
- const actions = state.actions || [];
316
- const allObservations = state.observations || [];
367
+ const actions = state.actions;
368
+ const allObservations = state.observations;
317
369
  const iteration = state.iteration || 0;
318
370
  const startTime = Date.now();
319
- actionLogger.debug("Action node started", {
371
+ debugIfVerbose(actionLogger, verbose, "Action node started", {
320
372
  actionCount: actions.length,
321
373
  iteration,
322
374
  cacheEnabled: enableDeduplication
@@ -335,7 +387,7 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
335
387
  }
336
388
  }
337
389
  if (cacheSize > 0) {
338
- actionLogger.debug("Deduplication cache built", {
390
+ debugIfVerbose(actionLogger, verbose, "Deduplication cache built", {
339
391
  cacheSize,
340
392
  totalObservations: allObservations.length
341
393
  });
@@ -346,7 +398,7 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
346
398
  for (const action of recentActions) {
347
399
  const existingObservation = allObservations.find((obs) => obs.toolCallId === action.id);
348
400
  if (existingObservation) {
349
- actionLogger.debug("Skipping already-processed action", {
401
+ debugIfVerbose(actionLogger, verbose, "Skipping already-processed action", {
350
402
  toolName: action.name,
351
403
  toolCallId: action.id,
352
404
  iteration
@@ -389,7 +441,7 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
389
441
  const result = await tool.invoke(action.arguments);
390
442
  const executionTime = Date.now() - startTime2;
391
443
  toolsExecuted++;
392
- actionLogger.debug("Tool executed successfully", {
444
+ debugIfVerbose(actionLogger, verbose, "Tool executed successfully", {
393
445
  toolName: action.name,
394
446
  executionTime,
395
447
  iteration
@@ -405,7 +457,7 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
405
457
  executionCache.set(cacheKey, observation);
406
458
  }
407
459
  } catch (error) {
408
- const errorMessage = handleNodeError(error, `action:${action.name}`, false);
460
+ const errorMessage = handleNodeError(error, `action:${action.name}`, verbose);
409
461
  actionLogger.error("Tool execution failed", {
410
462
  toolName: action.name,
411
463
  error: errorMessage,
@@ -434,39 +486,31 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
434
486
  }
435
487
  function createObservationNode(verbose = false, returnIntermediateSteps = false) {
436
488
  return async (state) => {
437
- const observations = state.observations || [];
438
- const thoughts = state.thoughts || [];
439
- const actions = state.actions || [];
489
+ const observations = state.observations;
490
+ const thoughts = state.thoughts;
491
+ const actions = state.actions;
440
492
  const iteration = state.iteration || 0;
441
- observationLogger.debug("Processing observations", {
493
+ debugIfVerbose(observationLogger, verbose, "Processing observations", {
442
494
  observationCount: observations.length,
443
495
  iteration
444
496
  });
445
497
  const recentObservations = observations.slice(-10);
446
498
  const latestActions = actions.slice(-10);
447
- const observationMessages = recentObservations.map((obs) => {
448
- const content = obs.error ? `Error: ${obs.error}` : typeof obs.result === "string" ? obs.result : JSON.stringify(obs.result, null, 2);
449
- return {
450
- role: "tool",
451
- content,
452
- name: latestActions.find((a) => a.id === obs.toolCallId)?.name,
453
- tool_call_id: obs.toolCallId
454
- // Include tool_call_id for proper ToolMessage construction
455
- };
456
- });
499
+ const actionNamesById = new Map(latestActions.map((action) => [action.id, action.name]));
500
+ const observationMessages = recentObservations.map((observation) => ({
501
+ role: "tool",
502
+ content: formatObservationContent(observation),
503
+ name: actionNamesById.get(observation.toolCallId),
504
+ tool_call_id: observation.toolCallId
505
+ }));
457
506
  const scratchpadEntries = returnIntermediateSteps ? [{
458
507
  step: state.iteration,
459
- thought: thoughts[thoughts.length - 1]?.content || "",
460
- action: latestActions.map((a) => `${a.name}(${JSON.stringify(a.arguments)})`).join(", "),
461
- observation: recentObservations.map((obs) => {
462
- if (obs.error) {
463
- return `Error: ${obs.error}`;
464
- }
465
- return typeof obs.result === "string" ? obs.result : JSON.stringify(obs.result);
466
- }).join("; "),
508
+ thought: getLatestThought(thoughts),
509
+ action: formatActionSummary(latestActions),
510
+ observation: formatObservationSummary(recentObservations),
467
511
  timestamp: Date.now()
468
512
  }] : [];
469
- observationLogger.debug("Observation node complete", {
513
+ debugIfVerbose(observationLogger, verbose, "Observation node complete", {
470
514
  iteration,
471
515
  scratchpadUpdated: returnIntermediateSteps,
472
516
  messageCount: observationMessages.length
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentforge/patterns",
3
- "version": "0.15.9",
3
+ "version": "0.15.10",
4
4
  "description": "Production-ready agent workflow patterns for TypeScript including ReAct and Planner-Executor, built on LangGraph.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -41,13 +41,13 @@
41
41
  "url": "https://github.com/TVScoundrel/agentforge/issues"
42
42
  },
43
43
  "dependencies": {
44
- "@agentforge/core": "0.15.9",
44
+ "@agentforge/core": "0.15.10",
45
45
  "@langchain/core": "^1.1.17",
46
46
  "@langchain/langgraph": "^1.1.2",
47
47
  "zod": "^3.23.8"
48
48
  },
49
49
  "devDependencies": {
50
- "@agentforge/testing": "0.15.9",
50
+ "@agentforge/testing": "0.15.10",
51
51
  "@eslint/js": "^9.17.0",
52
52
  "@types/node": "^22.10.2",
53
53
  "eslint": "^9.17.0",