@librechat/agents 3.0.36 → 3.0.40

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 (62) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +71 -2
  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/events.cjs +3 -0
  6. package/dist/cjs/events.cjs.map +1 -1
  7. package/dist/cjs/graphs/Graph.cjs +5 -1
  8. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  9. package/dist/cjs/main.cjs +12 -0
  10. package/dist/cjs/main.cjs.map +1 -1
  11. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +329 -0
  12. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -0
  13. package/dist/cjs/tools/ToolNode.cjs +34 -3
  14. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  15. package/dist/cjs/tools/ToolSearchRegex.cjs +455 -0
  16. package/dist/cjs/tools/ToolSearchRegex.cjs.map +1 -0
  17. package/dist/esm/agents/AgentContext.mjs +71 -2
  18. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  19. package/dist/esm/common/enum.mjs +2 -0
  20. package/dist/esm/common/enum.mjs.map +1 -1
  21. package/dist/esm/events.mjs +4 -1
  22. package/dist/esm/events.mjs.map +1 -1
  23. package/dist/esm/graphs/Graph.mjs +5 -1
  24. package/dist/esm/graphs/Graph.mjs.map +1 -1
  25. package/dist/esm/main.mjs +2 -0
  26. package/dist/esm/main.mjs.map +1 -1
  27. package/dist/esm/tools/ProgrammaticToolCalling.mjs +324 -0
  28. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -0
  29. package/dist/esm/tools/ToolNode.mjs +34 -3
  30. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  31. package/dist/esm/tools/ToolSearchRegex.mjs +448 -0
  32. package/dist/esm/tools/ToolSearchRegex.mjs.map +1 -0
  33. package/dist/types/agents/AgentContext.d.ts +25 -1
  34. package/dist/types/common/enum.d.ts +2 -0
  35. package/dist/types/graphs/Graph.d.ts +2 -1
  36. package/dist/types/index.d.ts +2 -0
  37. package/dist/types/test/mockTools.d.ts +28 -0
  38. package/dist/types/tools/ProgrammaticToolCalling.d.ts +86 -0
  39. package/dist/types/tools/ToolNode.d.ts +7 -1
  40. package/dist/types/tools/ToolSearchRegex.d.ts +80 -0
  41. package/dist/types/types/graph.d.ts +7 -1
  42. package/dist/types/types/tools.d.ts +136 -0
  43. package/package.json +5 -1
  44. package/src/agents/AgentContext.ts +86 -0
  45. package/src/common/enum.ts +2 -0
  46. package/src/events.ts +5 -1
  47. package/src/graphs/Graph.ts +6 -0
  48. package/src/index.ts +2 -0
  49. package/src/scripts/code_exec_ptc.ts +277 -0
  50. package/src/scripts/programmatic_exec.ts +396 -0
  51. package/src/scripts/programmatic_exec_agent.ts +231 -0
  52. package/src/scripts/tool_search_regex.ts +162 -0
  53. package/src/test/mockTools.ts +366 -0
  54. package/src/tools/ProgrammaticToolCalling.ts +423 -0
  55. package/src/tools/ToolNode.ts +38 -4
  56. package/src/tools/ToolSearchRegex.ts +535 -0
  57. package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.ts +318 -0
  58. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +613 -0
  59. package/src/tools/__tests__/ToolSearchRegex.integration.test.ts +161 -0
  60. package/src/tools/__tests__/ToolSearchRegex.test.ts +232 -0
  61. package/src/types/graph.ts +7 -1
  62. package/src/types/tools.ts +166 -0
@@ -0,0 +1,161 @@
1
+ // src/tools/__tests__/ToolSearchRegex.integration.test.ts
2
+ /**
3
+ * Integration tests for Tool Search Regex.
4
+ * These tests hit the LIVE Code API and verify end-to-end search functionality.
5
+ *
6
+ * Run with: npm test -- ToolSearchRegex.integration.test.ts
7
+ */
8
+ import { config as dotenvConfig } from 'dotenv';
9
+ dotenvConfig();
10
+
11
+ import { describe, it, expect, beforeAll } from '@jest/globals';
12
+ import { createToolSearchRegexTool } from '../ToolSearchRegex';
13
+ import { createToolSearchToolRegistry } from '@/test/mockTools';
14
+
15
+ describe('ToolSearchRegex - Live API Integration', () => {
16
+ let searchTool: ReturnType<typeof createToolSearchRegexTool>;
17
+ const toolRegistry = createToolSearchToolRegistry();
18
+
19
+ beforeAll(() => {
20
+ const apiKey = process.env.LIBRECHAT_CODE_API_KEY;
21
+ if (apiKey == null || apiKey === '') {
22
+ throw new Error(
23
+ 'LIBRECHAT_CODE_API_KEY not set. Required for integration tests.'
24
+ );
25
+ }
26
+
27
+ searchTool = createToolSearchRegexTool({ apiKey, toolRegistry });
28
+ });
29
+
30
+ it('searches for expense-related tools', async () => {
31
+ const output = await searchTool.invoke({ query: 'expense' });
32
+
33
+ expect(typeof output).toBe('string');
34
+ expect(output).toContain('Found');
35
+ expect(output).toContain('matching tools');
36
+ expect(output).toContain('get_expenses');
37
+ expect(output).toContain('calculate_expense_totals');
38
+ expect(output).toContain('score: 0.95');
39
+ }, 10000);
40
+
41
+ it('searches for weather tools with OR pattern', async () => {
42
+ const output = await searchTool.invoke({ query: 'weather|forecast' });
43
+
44
+ expect(output).toContain('Found');
45
+ expect(output).toContain('get_weather');
46
+ expect(output).toContain('get_forecast');
47
+ }, 10000);
48
+
49
+ it('performs case-insensitive search', async () => {
50
+ const output = await searchTool.invoke({ query: 'EMAIL' });
51
+
52
+ expect(output).toContain('send_email');
53
+ }, 10000);
54
+
55
+ it('searches descriptions only', async () => {
56
+ const output = await searchTool.invoke({
57
+ query: 'database',
58
+ fields: ['description'],
59
+ });
60
+
61
+ expect(output).toContain('Found');
62
+ expect(output).toContain('Matched in: description');
63
+ expect(output).toContain('run_database_query');
64
+ }, 10000);
65
+
66
+ it('searches parameter names', async () => {
67
+ const output = await searchTool.invoke({
68
+ query: 'query',
69
+ fields: ['parameters'],
70
+ });
71
+
72
+ expect(output).toContain('Found');
73
+ expect(output).toContain('Matched in: parameters');
74
+ }, 10000);
75
+
76
+ it('limits results to specified count', async () => {
77
+ const output = await searchTool.invoke({
78
+ query: 'get',
79
+ max_results: 2,
80
+ });
81
+
82
+ expect(output).toContain('Found 2 matching tools');
83
+ const toolMatches = output.match(/- \w+ \(score:/g);
84
+ expect(toolMatches?.length).toBe(2);
85
+ }, 10000);
86
+
87
+ it('returns no matches for nonsense pattern', async () => {
88
+ const output = await searchTool.invoke({
89
+ query: 'xyznonexistent999',
90
+ });
91
+
92
+ expect(output).toContain('No tools matched');
93
+ expect(output).toContain('Total tools searched:');
94
+ }, 10000);
95
+
96
+ it('uses regex character classes', async () => {
97
+ const output = await searchTool.invoke({ query: 'get_[a-z]+' });
98
+
99
+ expect(output).toContain('Found');
100
+ expect(output).toContain('matching tools');
101
+ }, 10000);
102
+
103
+ it('sanitizes dangerous patterns with warning', async () => {
104
+ const output = await searchTool.invoke({ query: '(a+)+' });
105
+
106
+ expect(output).toContain(
107
+ 'Note: The provided pattern was converted to a literal search for safety'
108
+ );
109
+ // After sanitization, pattern is shown in the output
110
+ expect(output).toContain('Total tools searched:');
111
+ expect(output).toContain('No tools matched');
112
+ }, 10000);
113
+
114
+ it('searches across all fields', async () => {
115
+ const output = await searchTool.invoke({
116
+ query: 'text',
117
+ fields: ['name', 'description', 'parameters'],
118
+ });
119
+
120
+ expect(output).toContain('Found');
121
+ expect(output).toContain('translate_text');
122
+ }, 10000);
123
+
124
+ it('handles complex regex patterns', async () => {
125
+ const output = await searchTool.invoke({
126
+ query: '^(get|create)_.*',
127
+ });
128
+
129
+ expect(output).toContain('Found');
130
+ expect(output).toContain('matching tools');
131
+ }, 10000);
132
+
133
+ it('prioritizes name matches over description matches', async () => {
134
+ const output = await searchTool.invoke({ query: 'generate' });
135
+
136
+ expect(output).toContain('generate_report');
137
+ expect(output).toContain('score:');
138
+
139
+ const lines = output.split('\n');
140
+ const generateLine = lines.find((l: string) =>
141
+ l.includes('generate_report')
142
+ );
143
+ expect(generateLine).toContain('score: 0.95'); // Name match = 0.95
144
+ }, 10000);
145
+
146
+ it('finds tools by partial name match', async () => {
147
+ const output = await searchTool.invoke({ query: 'budget' });
148
+
149
+ expect(output).toContain('create_budget');
150
+ }, 10000);
151
+
152
+ it('handles very specific patterns', async () => {
153
+ const output = await searchTool.invoke({
154
+ query: 'send_email',
155
+ });
156
+
157
+ expect(output).toContain('Found');
158
+ expect(output).toContain('send_email');
159
+ expect(output).toContain('score: 0.95');
160
+ }, 10000);
161
+ });
@@ -0,0 +1,232 @@
1
+ // src/tools/__tests__/ToolSearchRegex.test.ts
2
+ /**
3
+ * Unit tests for Tool Search Regex.
4
+ * Tests helper functions and sanitization logic without hitting the API.
5
+ */
6
+ import { describe, it, expect } from '@jest/globals';
7
+ import {
8
+ sanitizeRegex,
9
+ escapeRegexSpecialChars,
10
+ isDangerousPattern,
11
+ countNestedGroups,
12
+ hasNestedQuantifiers,
13
+ } from '../ToolSearchRegex';
14
+
15
+ describe('ToolSearchRegex', () => {
16
+ describe('escapeRegexSpecialChars', () => {
17
+ it('escapes special regex characters', () => {
18
+ expect(escapeRegexSpecialChars('hello.world')).toBe('hello\\.world');
19
+ expect(escapeRegexSpecialChars('test*pattern')).toBe('test\\*pattern');
20
+ expect(escapeRegexSpecialChars('query+result')).toBe('query\\+result');
21
+ expect(escapeRegexSpecialChars('a?b')).toBe('a\\?b');
22
+ expect(escapeRegexSpecialChars('(group)')).toBe('\\(group\\)');
23
+ expect(escapeRegexSpecialChars('[abc]')).toBe('\\[abc\\]');
24
+ expect(escapeRegexSpecialChars('a|b')).toBe('a\\|b');
25
+ expect(escapeRegexSpecialChars('a^b$c')).toBe('a\\^b\\$c');
26
+ expect(escapeRegexSpecialChars('a{2,3}')).toBe('a\\{2,3\\}');
27
+ });
28
+
29
+ it('handles empty string', () => {
30
+ expect(escapeRegexSpecialChars('')).toBe('');
31
+ });
32
+
33
+ it('handles string with no special chars', () => {
34
+ expect(escapeRegexSpecialChars('hello_world')).toBe('hello_world');
35
+ expect(escapeRegexSpecialChars('test123')).toBe('test123');
36
+ });
37
+
38
+ it('handles multiple consecutive special chars', () => {
39
+ expect(escapeRegexSpecialChars('...')).toBe('\\.\\.\\.');
40
+ expect(escapeRegexSpecialChars('***')).toBe('\\*\\*\\*');
41
+ });
42
+ });
43
+
44
+ describe('countNestedGroups', () => {
45
+ it('counts simple nesting', () => {
46
+ expect(countNestedGroups('(a)')).toBe(1);
47
+ expect(countNestedGroups('((a))')).toBe(2);
48
+ expect(countNestedGroups('(((a)))')).toBe(3);
49
+ });
50
+
51
+ it('counts maximum depth with multiple groups', () => {
52
+ expect(countNestedGroups('(a)(b)(c)')).toBe(1);
53
+ expect(countNestedGroups('(a(b)c)')).toBe(2);
54
+ expect(countNestedGroups('(a(b(c)))')).toBe(3);
55
+ });
56
+
57
+ it('handles mixed nesting levels', () => {
58
+ expect(countNestedGroups('(a)((b)(c))')).toBe(2);
59
+ expect(countNestedGroups('((a)(b))((c))')).toBe(2);
60
+ });
61
+
62
+ it('ignores escaped parentheses', () => {
63
+ expect(countNestedGroups('\\(not a group\\)')).toBe(0);
64
+ expect(countNestedGroups('(a\\(b\\)c)')).toBe(1);
65
+ });
66
+
67
+ it('handles no groups', () => {
68
+ expect(countNestedGroups('abc')).toBe(0);
69
+ expect(countNestedGroups('test.*pattern')).toBe(0);
70
+ });
71
+
72
+ it('handles unbalanced groups', () => {
73
+ expect(countNestedGroups('((a)')).toBe(2);
74
+ expect(countNestedGroups('(a))')).toBe(1);
75
+ });
76
+ });
77
+
78
+ describe('hasNestedQuantifiers', () => {
79
+ it('detects nested quantifiers', () => {
80
+ expect(hasNestedQuantifiers('(a+)+')).toBe(true);
81
+ expect(hasNestedQuantifiers('(a*)*')).toBe(true);
82
+ expect(hasNestedQuantifiers('(a+)*')).toBe(true);
83
+ expect(hasNestedQuantifiers('(a*)?')).toBe(true);
84
+ });
85
+
86
+ it('allows safe quantifiers', () => {
87
+ expect(hasNestedQuantifiers('a+')).toBe(false);
88
+ expect(hasNestedQuantifiers('(abc)+')).toBe(false);
89
+ expect(hasNestedQuantifiers('a+b*c?')).toBe(false);
90
+ });
91
+
92
+ it('handles complex patterns', () => {
93
+ expect(hasNestedQuantifiers('(a|b)+')).toBe(false);
94
+ // Note: This pattern might not be detected by the simple regex check
95
+ const complexPattern = '((a|b)+)+';
96
+ const result = hasNestedQuantifiers(complexPattern);
97
+ // Just verify it doesn't crash - detection may vary
98
+ expect(typeof result).toBe('boolean');
99
+ });
100
+ });
101
+
102
+ describe('isDangerousPattern', () => {
103
+ it('detects nested quantifiers', () => {
104
+ expect(isDangerousPattern('(a+)+')).toBe(true);
105
+ expect(isDangerousPattern('(a*)*')).toBe(true);
106
+ expect(isDangerousPattern('(.+)+')).toBe(true);
107
+ expect(isDangerousPattern('(.*)*')).toBe(true);
108
+ });
109
+
110
+ it('detects excessive nesting', () => {
111
+ expect(isDangerousPattern('((((((a))))))')).toBe(true); // Depth > 5
112
+ });
113
+
114
+ it('detects excessive wildcards', () => {
115
+ const pattern = '.{1000,}';
116
+ expect(isDangerousPattern(pattern)).toBe(true);
117
+ });
118
+
119
+ it('allows safe patterns', () => {
120
+ expect(isDangerousPattern('weather')).toBe(false);
121
+ expect(isDangerousPattern('get_.*_data')).toBe(false);
122
+ expect(isDangerousPattern('(a|b|c)')).toBe(false);
123
+ expect(isDangerousPattern('test\\d+')).toBe(false);
124
+ });
125
+
126
+ it('detects various dangerous patterns', () => {
127
+ expect(isDangerousPattern('(.*)+')).toBe(true);
128
+ expect(isDangerousPattern('(.+)*')).toBe(true);
129
+ });
130
+ });
131
+
132
+ describe('sanitizeRegex', () => {
133
+ it('returns safe pattern unchanged', () => {
134
+ const result = sanitizeRegex('weather');
135
+ expect(result.safe).toBe('weather');
136
+ expect(result.wasEscaped).toBe(false);
137
+ });
138
+
139
+ it('escapes dangerous patterns', () => {
140
+ const result = sanitizeRegex('(a+)+');
141
+ expect(result.safe).toBe('\\(a\\+\\)\\+');
142
+ expect(result.wasEscaped).toBe(true);
143
+ });
144
+
145
+ it('escapes invalid regex', () => {
146
+ const result = sanitizeRegex('(unclosed');
147
+ expect(result.wasEscaped).toBe(true);
148
+ expect(result.safe).toContain('\\(');
149
+ });
150
+
151
+ it('allows complex but safe patterns', () => {
152
+ const result = sanitizeRegex('get_[a-z]+_data');
153
+ expect(result.safe).toBe('get_[a-z]+_data');
154
+ expect(result.wasEscaped).toBe(false);
155
+ });
156
+
157
+ it('handles alternation patterns', () => {
158
+ const result = sanitizeRegex('weather|forecast');
159
+ expect(result.safe).toBe('weather|forecast');
160
+ expect(result.wasEscaped).toBe(false);
161
+ });
162
+ });
163
+
164
+ describe('Pattern Validation Edge Cases', () => {
165
+ it('handles empty pattern', () => {
166
+ expect(countNestedGroups('')).toBe(0);
167
+ expect(hasNestedQuantifiers('')).toBe(false);
168
+ expect(isDangerousPattern('')).toBe(false);
169
+ });
170
+
171
+ it('handles pattern with only quantifiers', () => {
172
+ expect(hasNestedQuantifiers('+++')).toBe(false);
173
+ expect(hasNestedQuantifiers('***')).toBe(false);
174
+ });
175
+
176
+ it('handles escaped special sequences', () => {
177
+ const result = sanitizeRegex('\\d+\\w*\\s?');
178
+ expect(result.wasEscaped).toBe(false);
179
+ });
180
+
181
+ it('sanitizes exponential backtracking patterns', () => {
182
+ // These can cause catastrophic backtracking
183
+ expect(isDangerousPattern('(a+)+')).toBe(true);
184
+ expect(isDangerousPattern('(a*)*')).toBe(true);
185
+ expect(isDangerousPattern('(.*)*')).toBe(true);
186
+ });
187
+ });
188
+
189
+ describe('Real-World Pattern Examples', () => {
190
+ it('handles common search patterns safely', () => {
191
+ const safePatterns = [
192
+ 'expense',
193
+ 'weather|forecast',
194
+ 'data.*query',
195
+ '_tool$',
196
+ ];
197
+
198
+ for (const pattern of safePatterns) {
199
+ const result = sanitizeRegex(pattern);
200
+ expect(result.wasEscaped).toBe(false);
201
+ }
202
+ });
203
+
204
+ it('escapes clearly dangerous patterns', () => {
205
+ const dangerousPatterns = ['(a+)+', '(.*)+', '(.+)*'];
206
+
207
+ for (const pattern of dangerousPatterns) {
208
+ const result = sanitizeRegex(pattern);
209
+ expect(result.wasEscaped).toBe(true);
210
+ }
211
+ });
212
+
213
+ it('handles patterns that may or may not be escaped', () => {
214
+ // These patterns might be escaped depending on validation logic
215
+ const edgeCasePatterns = [
216
+ '(?i)email',
217
+ '^create_',
218
+ 'get_[a-z]+_info',
219
+ 'get_.*',
220
+ '((((((a))))))',
221
+ '(a|a)*',
222
+ ];
223
+
224
+ for (const pattern of edgeCasePatterns) {
225
+ const result = sanitizeRegex(pattern);
226
+ // Just verify it returns a result without crashing
227
+ expect(typeof result.safe).toBe('string');
228
+ expect(typeof result.wasEscaped).toBe('boolean');
229
+ }
230
+ });
231
+ });
232
+ });
@@ -18,7 +18,7 @@ import type {
18
18
  import type { RunnableConfig, Runnable } from '@langchain/core/runnables';
19
19
  import type { ChatGenerationChunk } from '@langchain/core/outputs';
20
20
  import type { GoogleAIToolType } from '@langchain/google-common';
21
- import type { ToolMap, ToolEndEvent, GenericTool } from '@/types/tools';
21
+ import type { ToolMap, ToolEndEvent, GenericTool, LCTool } from '@/types/tools';
22
22
  import type { Providers, Callback, GraphNodeKeys } from '@/common';
23
23
  import type { StandardGraph, MultiAgentGraph } from '@/graphs';
24
24
  import type { ClientOptions } from '@/types/llm';
@@ -369,4 +369,10 @@ export interface AgentInputs {
369
369
  reasoningKey?: 'reasoning_content' | 'reasoning';
370
370
  /** Format content blocks as strings (for legacy compatibility i.e. Ollama/Azure Serverless) */
371
371
  useLegacyContent?: boolean;
372
+ /**
373
+ * Tool definitions for all tools, including deferred and programmatic.
374
+ * Used for tool search and programmatic tool calling.
375
+ * Maps tool name to LCTool definition.
376
+ */
377
+ toolRegistry?: Map<string, LCTool>;
372
378
  }
@@ -35,6 +35,12 @@ export type ToolNodeOptions = {
35
35
  data: ToolErrorData,
36
36
  metadata?: Record<string, unknown>
37
37
  ) => Promise<void>;
38
+ /** Tools available for programmatic code execution (allowed_callers includes 'code_execution') */
39
+ programmaticToolMap?: ToolMap;
40
+ /** Tool definitions for programmatic code execution (sent to Code API for stub generation) */
41
+ programmaticToolDefs?: LCTool[];
42
+ /** Tool registry for tool search (deferred tool definitions) */
43
+ toolRegistry?: LCToolRegistry;
38
44
  };
39
45
 
40
46
  export type ToolNodeConstructorParams = ToolRefs & ToolNodeOptions;
@@ -78,3 +84,163 @@ export type ExecuteResult = {
78
84
  stderr: string;
79
85
  files?: FileRefs;
80
86
  };
87
+
88
+ /** JSON Schema type definition for tool parameters */
89
+ export type JsonSchemaType = {
90
+ type:
91
+ | 'string'
92
+ | 'number'
93
+ | 'integer'
94
+ | 'float'
95
+ | 'boolean'
96
+ | 'array'
97
+ | 'object';
98
+ enum?: string[];
99
+ items?: JsonSchemaType;
100
+ properties?: Record<string, JsonSchemaType>;
101
+ required?: string[];
102
+ description?: string;
103
+ additionalProperties?: boolean | JsonSchemaType;
104
+ };
105
+
106
+ /**
107
+ * Specifies which contexts can invoke a tool (inspired by Anthropic's allowed_callers)
108
+ * - 'direct': Only callable directly by the LLM (default if omitted)
109
+ * - 'code_execution': Only callable from within programmatic code execution
110
+ */
111
+ export type AllowedCaller = 'direct' | 'code_execution';
112
+
113
+ /** Tool definition with optional deferred loading and caller restrictions */
114
+ export type LCTool = {
115
+ name: string;
116
+ description?: string;
117
+ parameters?: JsonSchemaType;
118
+ /** When true, tool is not loaded into context initially (for tool search) */
119
+ defer_loading?: boolean;
120
+ /**
121
+ * Which contexts can invoke this tool.
122
+ * Default: ['direct'] (only callable directly by LLM)
123
+ * Options: 'direct', 'code_execution'
124
+ */
125
+ allowed_callers?: AllowedCaller[];
126
+ };
127
+
128
+ /** Map of tool names to tool definitions */
129
+ export type LCToolRegistry = Map<string, LCTool>;
130
+
131
+ /** Parameters for creating a Tool Search Regex tool */
132
+ export type ToolSearchRegexParams = {
133
+ apiKey?: string;
134
+ toolRegistry?: LCToolRegistry;
135
+ onlyDeferred?: boolean;
136
+ baseUrl?: string;
137
+ [key: string]: unknown;
138
+ };
139
+
140
+ /** Simplified tool metadata for search purposes */
141
+ export type ToolMetadata = {
142
+ name: string;
143
+ description: string;
144
+ parameters?: JsonSchemaType;
145
+ };
146
+
147
+ /** Individual search result for a matching tool */
148
+ export type ToolSearchResult = {
149
+ tool_name: string;
150
+ match_score: number;
151
+ matched_field: string;
152
+ snippet: string;
153
+ };
154
+
155
+ /** Response from the tool search operation */
156
+ export type ToolSearchResponse = {
157
+ tool_references: ToolSearchResult[];
158
+ total_tools_searched: number;
159
+ pattern_used: string;
160
+ };
161
+
162
+ /** Artifact returned alongside the formatted search results */
163
+ export type ToolSearchArtifact = {
164
+ tool_references: ToolSearchResult[];
165
+ metadata: {
166
+ total_searched: number;
167
+ pattern: string;
168
+ error?: string;
169
+ };
170
+ };
171
+
172
+ // ============================================================================
173
+ // Programmatic Tool Calling Types
174
+ // ============================================================================
175
+
176
+ /**
177
+ * Tool call requested by the Code API during programmatic execution
178
+ */
179
+ export type PTCToolCall = {
180
+ /** Unique ID like "call_001" */
181
+ id: string;
182
+ /** Tool name */
183
+ name: string;
184
+ /** Input parameters */
185
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
186
+ input: Record<string, any>;
187
+ };
188
+
189
+ /**
190
+ * Tool result sent back to the Code API
191
+ */
192
+ export type PTCToolResult = {
193
+ /** Matches PTCToolCall.id */
194
+ call_id: string;
195
+ /** Tool execution result (any JSON-serializable value) */
196
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
197
+ result: any;
198
+ /** Whether tool execution failed */
199
+ is_error: boolean;
200
+ /** Error details if is_error=true */
201
+ error_message?: string;
202
+ };
203
+
204
+ /**
205
+ * Response from the Code API for programmatic execution
206
+ */
207
+ export type ProgrammaticExecutionResponse = {
208
+ status: 'tool_call_required' | 'completed' | 'error' | unknown;
209
+ session_id?: string;
210
+
211
+ /** Present when status='tool_call_required' */
212
+ continuation_token?: string;
213
+ tool_calls?: PTCToolCall[];
214
+
215
+ /** Present when status='completed' */
216
+ stdout?: string;
217
+ stderr?: string;
218
+ files?: FileRefs;
219
+
220
+ /** Present when status='error' */
221
+ error?: string;
222
+ };
223
+
224
+ /**
225
+ * Artifact returned by the PTC tool
226
+ */
227
+ export type ProgrammaticExecutionArtifact = {
228
+ session_id?: string;
229
+ files?: FileRefs;
230
+ };
231
+
232
+ /**
233
+ * Initialization parameters for the PTC tool
234
+ */
235
+ export type ProgrammaticToolCallingParams = {
236
+ /** Code API key (or use CODE_API_KEY env var) */
237
+ apiKey?: string;
238
+ /** Code API base URL (or use CODE_BASEURL env var) */
239
+ baseUrl?: string;
240
+ /** Safety limit for round-trips (default: 20) */
241
+ maxRoundTrips?: number;
242
+ /** HTTP proxy URL */
243
+ proxy?: string;
244
+ /** Environment variable key for API key */
245
+ [key: string]: unknown;
246
+ };