illuma-agents 1.0.49 → 1.0.51

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 (130) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +62 -14
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/common/enum.cjs +2 -0
  4. package/dist/cjs/common/enum.cjs.map +1 -1
  5. package/dist/cjs/graphs/Graph.cjs +48 -2
  6. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  7. package/dist/cjs/graphs/MultiAgentGraph.cjs +26 -17
  8. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  9. package/dist/cjs/llm/openai/index.cjs +1 -0
  10. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  11. package/dist/cjs/main.cjs +9 -17
  12. package/dist/cjs/main.cjs.map +1 -1
  13. package/dist/cjs/stream.cjs +0 -14
  14. package/dist/cjs/stream.cjs.map +1 -1
  15. package/dist/cjs/tools/CodeExecutor.cjs +37 -27
  16. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  17. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +22 -18
  18. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  19. package/dist/cjs/tools/ToolNode.cjs +100 -5
  20. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  21. package/dist/cjs/tools/ToolSearch.cjs +38 -31
  22. package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
  23. package/dist/cjs/tools/schema.cjs +31 -0
  24. package/dist/cjs/tools/schema.cjs.map +1 -0
  25. package/dist/cjs/tools/search/schema.cjs +25 -23
  26. package/dist/cjs/tools/search/schema.cjs.map +1 -1
  27. package/dist/cjs/tools/search/tool.cjs +9 -33
  28. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  29. package/dist/cjs/utils/schema.cjs +27 -0
  30. package/dist/cjs/utils/schema.cjs.map +1 -0
  31. package/dist/cjs/utils/title.cjs +28 -14
  32. package/dist/cjs/utils/title.cjs.map +1 -1
  33. package/dist/esm/agents/AgentContext.mjs +62 -14
  34. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  35. package/dist/esm/common/enum.mjs +2 -0
  36. package/dist/esm/common/enum.mjs.map +1 -1
  37. package/dist/esm/graphs/Graph.mjs +48 -2
  38. package/dist/esm/graphs/Graph.mjs.map +1 -1
  39. package/dist/esm/graphs/MultiAgentGraph.mjs +26 -17
  40. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  41. package/dist/esm/llm/openai/index.mjs +1 -0
  42. package/dist/esm/llm/openai/index.mjs.map +1 -1
  43. package/dist/esm/main.mjs +3 -4
  44. package/dist/esm/main.mjs.map +1 -1
  45. package/dist/esm/stream.mjs +0 -14
  46. package/dist/esm/stream.mjs.map +1 -1
  47. package/dist/esm/tools/CodeExecutor.mjs +37 -27
  48. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  49. package/dist/esm/tools/ProgrammaticToolCalling.mjs +22 -18
  50. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  51. package/dist/esm/tools/ToolNode.mjs +101 -6
  52. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  53. package/dist/esm/tools/ToolSearch.mjs +38 -31
  54. package/dist/esm/tools/ToolSearch.mjs.map +1 -1
  55. package/dist/esm/tools/schema.mjs +28 -0
  56. package/dist/esm/tools/schema.mjs.map +1 -0
  57. package/dist/esm/tools/search/schema.mjs +25 -23
  58. package/dist/esm/tools/search/schema.mjs.map +1 -1
  59. package/dist/esm/tools/search/tool.mjs +10 -34
  60. package/dist/esm/tools/search/tool.mjs.map +1 -1
  61. package/dist/esm/utils/schema.mjs +24 -0
  62. package/dist/esm/utils/schema.mjs.map +1 -0
  63. package/dist/esm/utils/title.mjs +28 -14
  64. package/dist/esm/utils/title.mjs.map +1 -1
  65. package/dist/types/agents/AgentContext.d.ts +13 -2
  66. package/dist/types/common/enum.d.ts +2 -0
  67. package/dist/types/index.d.ts +2 -1
  68. package/dist/types/tools/CodeExecutor.d.ts +1 -15
  69. package/dist/types/tools/ProgrammaticToolCalling.d.ts +1 -13
  70. package/dist/types/tools/ToolNode.d.ts +12 -1
  71. package/dist/types/tools/ToolSearch.d.ts +1 -15
  72. package/dist/types/tools/schema.d.ts +12 -0
  73. package/dist/types/tools/search/schema.d.ts +25 -7
  74. package/dist/types/tools/search/tool.d.ts +1 -52
  75. package/dist/types/tools/search/types.d.ts +5 -23
  76. package/dist/types/types/graph.d.ts +32 -1
  77. package/dist/types/types/tools.d.ts +55 -0
  78. package/dist/types/utils/index.d.ts +1 -0
  79. package/dist/types/utils/schema.d.ts +8 -0
  80. package/package.json +2 -4
  81. package/src/agents/AgentContext.test.ts +99 -0
  82. package/src/agents/AgentContext.ts +74 -20
  83. package/src/common/enum.ts +2 -0
  84. package/src/graphs/Graph.ts +56 -4
  85. package/src/graphs/MultiAgentGraph.ts +26 -17
  86. package/src/index.ts +2 -3
  87. package/src/scripts/test_code_api.ts +4 -4
  88. package/src/specs/agent-handoffs.test.ts +1 -2
  89. package/src/specs/azure.simple.test.ts +214 -175
  90. package/src/specs/thinking-prune.test.ts +6 -6
  91. package/src/specs/tool-error.test.ts +7 -2
  92. package/src/stream.ts +0 -17
  93. package/src/test/mockTools.ts +34 -14
  94. package/src/tools/CodeExecutor.ts +48 -31
  95. package/src/tools/ProgrammaticToolCalling.ts +25 -24
  96. package/src/tools/ToolNode.ts +137 -15
  97. package/src/tools/ToolSearch.ts +55 -44
  98. package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.ts +10 -9
  99. package/src/tools/__tests__/ToolSearch.integration.test.ts +10 -9
  100. package/src/tools/schema.ts +37 -0
  101. package/src/tools/search/schema.ts +30 -25
  102. package/src/tools/search/tool.ts +23 -16
  103. package/src/tools/search/types.ts +5 -29
  104. package/src/types/graph.ts +33 -1
  105. package/src/types/tools.ts +58 -0
  106. package/src/utils/index.ts +1 -0
  107. package/src/utils/schema.ts +35 -0
  108. package/src/utils/title.ts +31 -19
  109. package/LICENSE +0 -21
  110. package/dist/cjs/deepagents/DeepAgentBackend.cjs +0 -170
  111. package/dist/cjs/deepagents/DeepAgentBackend.cjs.map +0 -1
  112. package/dist/cjs/deepagents/DeepAgentRuntime.cjs +0 -135
  113. package/dist/cjs/deepagents/DeepAgentRuntime.cjs.map +0 -1
  114. package/dist/cjs/deepagents/types.cjs +0 -29
  115. package/dist/cjs/deepagents/types.cjs.map +0 -1
  116. package/dist/esm/deepagents/DeepAgentBackend.mjs +0 -168
  117. package/dist/esm/deepagents/DeepAgentBackend.mjs.map +0 -1
  118. package/dist/esm/deepagents/DeepAgentRuntime.mjs +0 -132
  119. package/dist/esm/deepagents/DeepAgentRuntime.mjs.map +0 -1
  120. package/dist/esm/deepagents/types.mjs +0 -26
  121. package/dist/esm/deepagents/types.mjs.map +0 -1
  122. package/dist/types/deepagents/DeepAgentBackend.d.ts +0 -82
  123. package/dist/types/deepagents/DeepAgentRuntime.d.ts +0 -46
  124. package/dist/types/deepagents/index.d.ts +0 -16
  125. package/dist/types/deepagents/types.d.ts +0 -105
  126. package/src/deepagents/DeepAgentBackend.ts +0 -214
  127. package/src/deepagents/DeepAgentRuntime.ts +0 -187
  128. package/src/deepagents/index.ts +0 -25
  129. package/src/deepagents/types.ts +0 -118
  130. package/src/specs/deepagents.test.ts +0 -286
@@ -0,0 +1,12 @@
1
+ import { type StructuredToolInterface } from '@langchain/core/tools';
2
+ import type { LCTool } from '@/types';
3
+ /**
4
+ * Creates a schema-only tool for LLM binding in event-driven mode.
5
+ * These tools have valid schemas for the LLM to understand but should
6
+ * never be invoked directly - ToolNode handles execution via events.
7
+ */
8
+ export declare function createSchemaOnlyTool(definition: LCTool): StructuredToolInterface;
9
+ /**
10
+ * Creates schema-only tools for all definitions in an array.
11
+ */
12
+ export declare function createSchemaOnlyTools(definitions: LCTool[]): StructuredToolInterface[];
@@ -1,4 +1,3 @@
1
- import { z } from 'zod';
2
1
  export declare enum DATE_RANGE {
3
2
  PAST_HOUR = "h",
4
3
  PAST_24_HOURS = "d",
@@ -8,9 +7,28 @@ export declare enum DATE_RANGE {
8
7
  }
9
8
  export declare const DEFAULT_QUERY_DESCRIPTION: string;
10
9
  export declare const DEFAULT_COUNTRY_DESCRIPTION: string;
11
- export declare const querySchema: z.ZodString;
12
- export declare const dateSchema: z.ZodOptional<z.ZodNativeEnum<typeof DATE_RANGE>>;
13
- export declare const countrySchema: z.ZodOptional<z.ZodString>;
14
- export declare const imagesSchema: z.ZodOptional<z.ZodBoolean>;
15
- export declare const videosSchema: z.ZodOptional<z.ZodBoolean>;
16
- export declare const newsSchema: z.ZodOptional<z.ZodBoolean>;
10
+ export declare const querySchema: {
11
+ readonly type: "string";
12
+ readonly description: string;
13
+ };
14
+ export declare const dateSchema: {
15
+ readonly type: "string";
16
+ readonly enum: DATE_RANGE[];
17
+ readonly description: "Date range for search results.";
18
+ };
19
+ export declare const countrySchema: {
20
+ readonly type: "string";
21
+ readonly description: string;
22
+ };
23
+ export declare const imagesSchema: {
24
+ readonly type: "boolean";
25
+ readonly description: "Whether to also run an image search.";
26
+ };
27
+ export declare const videosSchema: {
28
+ readonly type: "boolean";
29
+ readonly description: "Whether to also run a video search.";
30
+ };
31
+ export declare const newsSchema: {
32
+ readonly type: "boolean";
33
+ readonly description: "Whether to also run a news search.";
34
+ };
@@ -1,54 +1,3 @@
1
- import { z } from 'zod';
2
1
  import { DynamicStructuredTool } from '@langchain/core/tools';
3
2
  import type * as t from './types';
4
- import { DATE_RANGE } from './schema';
5
- /**
6
- * Creates a search tool with a schema that dynamically includes the country field
7
- * only when the searchProvider is 'serper'.
8
- *
9
- * Supports multiple scraper providers:
10
- * - Firecrawl (default): Full-featured web scraping with multiple formats
11
- * - Serper: Lightweight scraping using Serper's scrape API
12
- *
13
- * @example
14
- * ```typescript
15
- * // Using Firecrawl scraper (default)
16
- * const searchTool = createSearchTool({
17
- * searchProvider: 'serper',
18
- * scraperProvider: 'firecrawl',
19
- * firecrawlApiKey: 'your-firecrawl-key'
20
- * });
21
- *
22
- * // Using Serper scraper
23
- * const searchTool = createSearchTool({
24
- * searchProvider: 'serper',
25
- * scraperProvider: 'serper',
26
- * serperApiKey: 'your-serper-key'
27
- * });
28
- * ```
29
- *
30
- * @param config - The search tool configuration
31
- * @returns A DynamicStructuredTool with a schema that depends on the searchProvider
32
- */
33
- export declare const createSearchTool: (config?: t.SearchToolConfig) => DynamicStructuredTool<z.ZodObject<{
34
- query: z.ZodString;
35
- date: z.ZodOptional<z.ZodNativeEnum<typeof DATE_RANGE>>;
36
- country?: z.ZodOptional<z.ZodString>;
37
- images: z.ZodOptional<z.ZodBoolean>;
38
- videos: z.ZodOptional<z.ZodBoolean>;
39
- news: z.ZodOptional<z.ZodBoolean>;
40
- }, "strip", z.ZodTypeAny, {
41
- query: string;
42
- videos?: boolean | undefined;
43
- images?: boolean | undefined;
44
- news?: boolean | undefined;
45
- date?: DATE_RANGE | undefined;
46
- country?: unknown;
47
- }, {
48
- query: string;
49
- videos?: boolean | undefined;
50
- images?: boolean | undefined;
51
- news?: boolean | undefined;
52
- date?: DATE_RANGE | undefined;
53
- country?: unknown;
54
- }>>;
3
+ export declare const createSearchTool: (config?: t.SearchToolConfig) => DynamicStructuredTool;
@@ -1,4 +1,3 @@
1
- import { z } from 'zod';
2
1
  import type { Logger as WinstonLogger } from 'winston';
3
2
  import type { RunnableConfig } from '@langchain/core/runnables';
4
3
  import type { BaseReranker } from './rerankers';
@@ -569,25 +568,8 @@ export type ProcessSourcesFields = {
569
568
  /** Skip scraping if content was already extracted directly (e.g., direct URL extraction) */
570
569
  skipScraping?: boolean;
571
570
  };
572
- export type SearchToolSchema = z.ZodObject<{
573
- query: z.ZodString;
574
- date: z.ZodOptional<z.ZodNativeEnum<typeof DATE_RANGE>>;
575
- country?: z.ZodOptional<z.ZodString>;
576
- images: z.ZodOptional<z.ZodBoolean>;
577
- videos: z.ZodOptional<z.ZodBoolean>;
578
- news: z.ZodOptional<z.ZodBoolean>;
579
- }, 'strip', z.ZodTypeAny, {
580
- query: string;
581
- date?: DATE_RANGE;
582
- country?: unknown;
583
- images?: boolean;
584
- videos?: boolean;
585
- news?: boolean;
586
- }, {
587
- query: string;
588
- date?: DATE_RANGE;
589
- country?: unknown;
590
- images?: boolean;
591
- videos?: boolean;
592
- news?: boolean;
593
- }>;
571
+ export interface SearchToolSchema {
572
+ type: 'object';
573
+ properties: Record<string, unknown>;
574
+ required: string[];
575
+ }
@@ -297,6 +297,24 @@ export interface StructuredOutputConfig {
297
297
  */
298
298
  includeRaw?: boolean;
299
299
  }
300
+ /**
301
+ * Database/API structured output format (snake_case with enabled flag).
302
+ * This matches the format stored in MongoDB and sent from frontends.
303
+ */
304
+ export interface StructuredOutputInput {
305
+ /** Whether structured output is enabled */
306
+ enabled?: boolean;
307
+ /** JSON Schema defining the expected response structure */
308
+ schema?: Record<string, unknown>;
309
+ /** Name identifier for the structured output */
310
+ name?: string;
311
+ /** Description of what the structured output represents */
312
+ description?: string;
313
+ /** Mode for structured output: 'tool' | 'provider' | 'auto' */
314
+ mode?: StructuredOutputMode;
315
+ /** Whether to enforce strict schema validation */
316
+ strict?: boolean;
317
+ }
300
318
  export interface AgentInputs {
301
319
  agentId: string;
302
320
  /** Human-readable name for the agent (used in handoff context). Defaults to agentId if not provided. */
@@ -327,9 +345,22 @@ export interface AgentInputs {
327
345
  */
328
346
  dynamicContext?: string;
329
347
  /**
330
- * Structured output configuration.
348
+ * Structured output configuration (camelCase).
331
349
  * When set, disables streaming and returns a validated JSON response
332
350
  * conforming to the specified schema.
333
351
  */
334
352
  structuredOutput?: StructuredOutputConfig;
353
+ /**
354
+ * Structured output configuration (snake_case - database/API format).
355
+ * Alternative to structuredOutput for compatibility with MongoDB/frontend.
356
+ * Uses an `enabled` flag to control activation.
357
+ * @deprecated Use structuredOutput instead when possible
358
+ */
359
+ structured_output?: StructuredOutputInput;
360
+ /**
361
+ * Serializable tool definitions for event-driven execution.
362
+ * When provided, ToolNode operates in event-driven mode, dispatching
363
+ * ON_TOOL_EXECUTE events instead of invoking tools directly.
364
+ */
365
+ toolDefinitions?: LCTool[];
335
366
  }
@@ -31,6 +31,12 @@ export type ToolNodeOptions = {
31
31
  toolRegistry?: LCToolRegistry;
32
32
  /** Reference to Graph's sessions map for automatic session injection */
33
33
  sessions?: ToolSessionMap;
34
+ /** When true, dispatches ON_TOOL_EXECUTE events instead of invoking tools directly */
35
+ eventDrivenMode?: boolean;
36
+ /** Tool definitions for event-driven mode (used for context, not invocation) */
37
+ toolDefinitions?: Map<string, LCTool>;
38
+ /** Agent ID for event-driven mode (used to identify which agent's context to use) */
39
+ agentId?: string;
34
40
  };
35
41
  export type ToolNodeConstructorParams = ToolRefs & ToolNodeOptions;
36
42
  export type ToolEndEvent = {
@@ -96,6 +102,55 @@ export type LCTool = {
96
102
  * Options: 'direct', 'code_execution'
97
103
  */
98
104
  allowed_callers?: AllowedCaller[];
105
+ /** Response format for the tool output */
106
+ responseFormat?: 'content' | 'content_and_artifact';
107
+ /** Server name for MCP tools */
108
+ serverName?: string;
109
+ /** Tool type classification */
110
+ toolType?: 'builtin' | 'mcp' | 'action';
111
+ };
112
+ /** Single tool call within a batch request for event-driven execution */
113
+ export type ToolCallRequest = {
114
+ /** Tool call ID from the LLM */
115
+ id: string;
116
+ /** Tool name */
117
+ name: string;
118
+ /** Tool arguments */
119
+ args: Record<string, unknown>;
120
+ /** Step ID for tracking */
121
+ stepId?: string;
122
+ /** Usage turn count for this tool */
123
+ turn?: number;
124
+ };
125
+ /** Batch request containing ALL tool calls for a graph step */
126
+ export type ToolExecuteBatchRequest = {
127
+ /** All tool calls from the AIMessage */
128
+ toolCalls: ToolCallRequest[];
129
+ /** User ID for context */
130
+ userId?: string;
131
+ /** Agent ID for context */
132
+ agentId?: string;
133
+ /** Runtime configurable from RunnableConfig (includes user, userMCPAuthMap, etc.) */
134
+ configurable?: Record<string, unknown>;
135
+ /** Runtime metadata from RunnableConfig (includes thread_id, run_id, provider, etc.) */
136
+ metadata?: Record<string, unknown>;
137
+ /** Promise resolver - handler calls this with ALL results */
138
+ resolve: (results: ToolExecuteResult[]) => void;
139
+ /** Promise rejector - handler calls this on fatal error */
140
+ reject: (error: Error) => void;
141
+ };
142
+ /** Result for a single tool call in event-driven execution */
143
+ export type ToolExecuteResult = {
144
+ /** Matches ToolCallRequest.id */
145
+ toolCallId: string;
146
+ /** Tool output content */
147
+ content: string | unknown[];
148
+ /** Optional artifact (for content_and_artifact format) */
149
+ artifact?: unknown;
150
+ /** Execution status */
151
+ status: 'success' | 'error';
152
+ /** Error message if status is 'error' */
153
+ errorMessage?: string;
99
154
  };
100
155
  /** Map of tool names to tool definitions */
101
156
  export type LCToolRegistry = Map<string, LCTool>;
@@ -6,3 +6,4 @@ export * from './run';
6
6
  export * from './tokens';
7
7
  export * from './toonFormat';
8
8
  export * from './contextAnalytics';
9
+ export * from './schema';
@@ -0,0 +1,8 @@
1
+ import type { ZodTypeAny } from 'zod';
2
+ /** Checks if a schema is a Zod schema by looking for the _def property */
3
+ export declare function isZodSchema(schema: unknown): schema is ZodTypeAny;
4
+ /**
5
+ * Converts a schema to JSON schema format.
6
+ * Handles both Zod schemas (converts) and JSON schemas (passthrough).
7
+ */
8
+ export declare function toJsonSchema(schema: unknown, name?: string, description?: string): Record<string, unknown>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "illuma-agents",
3
- "version": "1.0.49",
3
+ "version": "1.0.51",
4
4
  "main": "./dist/cjs/main.cjs",
5
5
  "module": "./dist/esm/main.mjs",
6
6
  "types": "./dist/types/index.d.ts",
@@ -18,7 +18,7 @@
18
18
  "url": "https://github.com/codevakure/agents"
19
19
  },
20
20
  "author": "Illuma Team",
21
- "license": "MIT",
21
+ "license": "UNLICENSED",
22
22
  "packageManager": "npm@10.5.2",
23
23
  "engines": {
24
24
  "node": ">=14.0.0"
@@ -26,7 +26,6 @@
26
26
  "files": [
27
27
  "dist",
28
28
  "src",
29
- "LICENSE",
30
29
  "README.md"
31
30
  ],
32
31
  "scripts": {
@@ -118,7 +117,6 @@
118
117
  "@langchain/openai": "0.5.18",
119
118
  "@langchain/textsplitters": "^0.1.0",
120
119
  "@langchain/xai": "^0.0.3",
121
- "deepagents": "^1.5.1",
122
120
  "@langfuse/langchain": "^4.3.0",
123
121
  "@langfuse/otel": "^4.3.0",
124
122
  "@langfuse/tracing": "^4.3.0",
@@ -68,6 +68,105 @@ describe('AgentContext', () => {
68
68
  const context = AgentContext.fromConfig(config);
69
69
  expect(context.isStructuredOutputMode).toBe(true);
70
70
  });
71
+
72
+ // Snake_case structured_output tests (database/API format)
73
+ it('should return true when structured_output (snake_case) is enabled with schema', () => {
74
+ const config: t.AgentInputs = {
75
+ ...baseConfig,
76
+ structured_output: {
77
+ enabled: true,
78
+ schema: {
79
+ type: 'object',
80
+ properties: {
81
+ result: { type: 'string' },
82
+ },
83
+ },
84
+ },
85
+ };
86
+ const context = AgentContext.fromConfig(config);
87
+ expect(context.isStructuredOutputMode).toBe(true);
88
+ });
89
+
90
+ it('should return false when structured_output is not enabled', () => {
91
+ const config: t.AgentInputs = {
92
+ ...baseConfig,
93
+ structured_output: {
94
+ enabled: false,
95
+ schema: {
96
+ type: 'object',
97
+ properties: {
98
+ result: { type: 'string' },
99
+ },
100
+ },
101
+ },
102
+ };
103
+ const context = AgentContext.fromConfig(config);
104
+ expect(context.isStructuredOutputMode).toBe(false);
105
+ });
106
+
107
+ it('should return false when structured_output has no schema', () => {
108
+ const config: t.AgentInputs = {
109
+ ...baseConfig,
110
+ structured_output: {
111
+ enabled: true,
112
+ // No schema
113
+ },
114
+ };
115
+ const context = AgentContext.fromConfig(config);
116
+ expect(context.isStructuredOutputMode).toBe(false);
117
+ });
118
+
119
+ it('should prefer structuredOutput (camelCase) over structured_output (snake_case)', () => {
120
+ const config: t.AgentInputs = {
121
+ ...baseConfig,
122
+ structuredOutput: {
123
+ schema: {
124
+ type: 'object',
125
+ properties: { camel: { type: 'string' } },
126
+ },
127
+ name: 'CamelSchema',
128
+ },
129
+ structured_output: {
130
+ enabled: true,
131
+ schema: {
132
+ type: 'object',
133
+ properties: { snake: { type: 'string' } },
134
+ },
135
+ name: 'SnakeSchema',
136
+ },
137
+ };
138
+ const context = AgentContext.fromConfig(config);
139
+ expect(context.isStructuredOutputMode).toBe(true);
140
+ expect(context.structuredOutput?.name).toBe('CamelSchema');
141
+ });
142
+
143
+ it('should convert structured_output properties correctly', () => {
144
+ const config: t.AgentInputs = {
145
+ ...baseConfig,
146
+ structured_output: {
147
+ enabled: true,
148
+ schema: {
149
+ type: 'object',
150
+ properties: { data: { type: 'string' } },
151
+ },
152
+ name: 'TestResponse',
153
+ description: 'A test response',
154
+ mode: 'provider',
155
+ strict: true,
156
+ },
157
+ };
158
+ const context = AgentContext.fromConfig(config);
159
+ expect(context.structuredOutput).toEqual({
160
+ schema: {
161
+ type: 'object',
162
+ properties: { data: { type: 'string' } },
163
+ },
164
+ name: 'TestResponse',
165
+ description: 'A test response',
166
+ mode: 'provider',
167
+ strict: true,
168
+ });
169
+ });
71
170
  });
72
171
 
73
172
  describe('getStructuredOutputSchema', () => {
@@ -1,6 +1,5 @@
1
1
  /* eslint-disable no-console */
2
2
  // src/agents/AgentContext.ts
3
- import { zodToJsonSchema } from 'zod-to-json-schema';
4
3
  import { SystemMessage } from '@langchain/core/messages';
5
4
  import { RunnableLambda } from '@langchain/core/runnables';
6
5
  import type {
@@ -11,7 +10,9 @@ import type {
11
10
  import type { RunnableConfig, Runnable } from '@langchain/core/runnables';
12
11
  import type * as t from '@/types';
13
12
  import type { createPruneMessages } from '@/messages';
13
+ import { createSchemaOnlyTools } from '@/tools/schema';
14
14
  import { ContentTypes, Providers } from '@/common';
15
+ import { toJsonSchema } from '@/utils/schema';
15
16
 
16
17
  /**
17
18
  * Encapsulates agent-specific state that can vary between agents in a multi-agent system
@@ -34,6 +35,7 @@ export class AgentContext {
34
35
  toolMap,
35
36
  toolEnd,
36
37
  toolRegistry,
38
+ toolDefinitions,
37
39
  instructions,
38
40
  additional_instructions,
39
41
  streamBuffer,
@@ -41,9 +43,27 @@ export class AgentContext {
41
43
  reasoningKey,
42
44
  useLegacyContent,
43
45
  dynamicContext,
44
- structuredOutput,
46
+ structuredOutput: structuredOutputCamel,
47
+ // eslint-disable-next-line @typescript-eslint/naming-convention
48
+ structured_output: structuredOutputSnake,
45
49
  } = agentConfig;
46
50
 
51
+ // Normalize structured output: support both camelCase and snake_case inputs
52
+ // Priority: structuredOutput (camelCase) > structured_output (snake_case with enabled check)
53
+ let structuredOutput: t.StructuredOutputConfig | undefined;
54
+ if (structuredOutputCamel) {
55
+ structuredOutput = structuredOutputCamel;
56
+ } else if (structuredOutputSnake?.enabled && structuredOutputSnake.schema) {
57
+ // Convert snake_case input to StructuredOutputConfig
58
+ structuredOutput = {
59
+ schema: structuredOutputSnake.schema,
60
+ name: structuredOutputSnake.name,
61
+ description: structuredOutputSnake.description,
62
+ mode: structuredOutputSnake.mode,
63
+ strict: structuredOutputSnake.strict,
64
+ };
65
+ }
66
+
47
67
  const agentContext = new AgentContext({
48
68
  agentId,
49
69
  name: name ?? agentId,
@@ -54,6 +74,7 @@ export class AgentContext {
54
74
  tools,
55
75
  toolMap,
56
76
  toolRegistry,
77
+ toolDefinitions,
57
78
  instructions,
58
79
  additionalInstructions: additional_instructions,
59
80
  reasoningKey,
@@ -122,6 +143,11 @@ export class AgentContext {
122
143
  * Used for tool search and programmatic tool calling.
123
144
  */
124
145
  toolRegistry?: t.LCToolRegistry;
146
+ /**
147
+ * Serializable tool definitions for event-driven execution.
148
+ * When provided, ToolNode operates in event-driven mode.
149
+ */
150
+ toolDefinitions?: t.LCTool[];
125
151
  /** Set of tool names discovered via tool search (to be loaded) */
126
152
  discoveredToolNames: Set<string> = new Set();
127
153
  /** Instructions for this agent */
@@ -207,6 +233,7 @@ export class AgentContext {
207
233
  tools,
208
234
  toolMap,
209
235
  toolRegistry,
236
+ toolDefinitions,
210
237
  instructions,
211
238
  additionalInstructions,
212
239
  dynamicContext,
@@ -226,6 +253,7 @@ export class AgentContext {
226
253
  tools?: t.GraphTools;
227
254
  toolMap?: t.ToolMap;
228
255
  toolRegistry?: t.LCToolRegistry;
256
+ toolDefinitions?: t.LCTool[];
229
257
  instructions?: string;
230
258
  additionalInstructions?: string;
231
259
  dynamicContext?: string;
@@ -245,6 +273,7 @@ export class AgentContext {
245
273
  this.tools = tools;
246
274
  this.toolMap = toolMap;
247
275
  this.toolRegistry = toolRegistry;
276
+ this.toolDefinitions = toolDefinitions;
248
277
  this.instructions = instructions;
249
278
  this.additionalInstructions = additionalInstructions;
250
279
  this.dynamicContext = dynamicContext;
@@ -600,15 +629,10 @@ export class AgentContext {
600
629
  genericTool.schema != null &&
601
630
  typeof genericTool.schema === 'object'
602
631
  ) {
603
- const schema = genericTool.schema as {
604
- describe: (desc: string) => unknown;
605
- };
606
- const describedSchema = schema.describe(
607
- (genericTool.description as string) || ''
608
- );
609
- const jsonSchema = zodToJsonSchema(
610
- describedSchema as Parameters<typeof zodToJsonSchema>[0],
611
- (genericTool.name as string) || ''
632
+ const jsonSchema = toJsonSchema(
633
+ genericTool.schema,
634
+ (genericTool.name as string | undefined) ?? '',
635
+ (genericTool.description as string | undefined) ?? ''
612
636
  );
613
637
  const toolName = (genericTool.name as string) || 'unknown';
614
638
  const tokens = tokenCounter(
@@ -777,40 +801,70 @@ export class AgentContext {
777
801
 
778
802
  /**
779
803
  * Gets tools that should be bound to the LLM.
780
- * Includes:
804
+ * In event-driven mode (toolDefinitions present, tools empty), creates schema-only tools.
805
+ * Otherwise filters tool instances based on:
781
806
  * 1. Non-deferred tools with allowed_callers: ['direct']
782
807
  * 2. Discovered tools (from tool search)
783
808
  * @returns Array of tools to bind to model
784
809
  */
785
810
  getToolsForBinding(): t.GraphTools | undefined {
811
+ /** Event-driven mode: create schema-only tools from definitions */
812
+ if (this.toolDefinitions && this.toolDefinitions.length > 0) {
813
+ return this.getEventDrivenToolsForBinding();
814
+ }
815
+
816
+ /** Traditional mode: filter actual tool instances */
786
817
  if (!this.tools || !this.toolRegistry) {
787
818
  return this.tools;
788
819
  }
789
820
 
790
- const toolsToInclude = this.tools.filter((tool) => {
821
+ return this.filterToolsForBinding(this.tools);
822
+ }
823
+
824
+ /** Creates schema-only tools from toolDefinitions for event-driven mode */
825
+ private getEventDrivenToolsForBinding(): t.GraphTools {
826
+ if (!this.toolDefinitions) {
827
+ return [];
828
+ }
829
+
830
+ const defsToInclude = this.toolDefinitions.filter((def) => {
831
+ const allowedCallers = def.allowed_callers ?? ['direct'];
832
+ if (!allowedCallers.includes('direct')) {
833
+ return false;
834
+ }
835
+ if (
836
+ def.defer_loading === true &&
837
+ !this.discoveredToolNames.has(def.name)
838
+ ) {
839
+ return false;
840
+ }
841
+ return true;
842
+ });
843
+
844
+ return createSchemaOnlyTools(defsToInclude) as t.GraphTools;
845
+ }
846
+
847
+ /** Filters tool instances for binding based on registry config */
848
+ private filterToolsForBinding(tools: t.GraphTools): t.GraphTools {
849
+ return tools.filter((tool) => {
791
850
  if (!('name' in tool)) {
792
- return true; // No name, include by default
851
+ return true;
793
852
  }
794
853
 
795
854
  const toolDef = this.toolRegistry?.get(tool.name);
796
855
  if (!toolDef) {
797
- return true; // Not in registry, include by default
856
+ return true;
798
857
  }
799
858
 
800
- // Check if discovered (overrides defer_loading)
801
859
  if (this.discoveredToolNames.has(tool.name)) {
802
- // Discovered tools must still have allowed_callers: ['direct']
803
860
  const allowedCallers = toolDef.allowed_callers ?? ['direct'];
804
861
  return allowedCallers.includes('direct');
805
862
  }
806
863
 
807
- // Not discovered: must be direct-callable AND not deferred
808
864
  const allowedCallers = toolDef.allowed_callers ?? ['direct'];
809
865
  return (
810
866
  allowedCallers.includes('direct') && toolDef.defer_loading !== true
811
867
  );
812
868
  });
813
-
814
- return toolsToInclude;
815
869
  }
816
870
  }
@@ -23,6 +23,8 @@ export enum GraphEvents {
23
23
  ON_CONTEXT_ANALYTICS = 'on_context_analytics',
24
24
  /** [Custom] Structured output event - emitted when agent returns structured JSON */
25
25
  ON_STRUCTURED_OUTPUT = 'on_structured_output',
26
+ /** [Custom] Request to execute tools - dispatched by ToolNode, handled by host */
27
+ ON_TOOL_EXECUTE = 'on_tool_execute',
26
28
 
27
29
  /* Official Events */
28
30