@elevasis/core 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +195 -3
- package/dist/organization-model/index.js +195 -3
- package/package.json +1 -1
- package/src/__tests__/template-foundations-compatibility.test.ts +95 -14
- package/src/auth/multi-tenancy/types.ts +2 -1
- package/src/execution/engine/__tests__/fixtures/test-agents.ts +4 -4
- package/src/execution/engine/index.ts +5 -19
- package/src/execution/engine/tools/platform/index.ts +9 -33
- package/src/execution/engine/tools/registry.ts +109 -2
- package/src/execution/engine/tools/tool-maps.ts +88 -0
- package/src/organization-model/README.md +19 -4
- package/src/organization-model/__tests__/graph.test.ts +612 -0
- package/src/organization-model/__tests__/resolve.test.ts +208 -0
- package/src/organization-model/defaults.ts +1 -1
- package/src/organization-model/organization-graph.mdx +262 -0
- package/src/organization-model/organization-model.mdx +257 -0
- package/src/organization-model/resolve.ts +26 -2
- package/src/organization-model/schema.ts +203 -1
- package/src/platform/constants/versions.ts +1 -1
- package/src/platform/registry/__tests__/resource-registry.integration.test.ts +24 -0
- package/src/platform/registry/__tests__/resource-registry.test.ts +63 -0
- package/src/platform/registry/resource-registry.ts +98 -10
- package/src/projects/api-schemas.ts +2 -1
- package/src/reference/_generated/contracts.md +1044 -0
- package/src/reference/glossary.md +88 -0
- package/src/server.ts +2 -3
- package/src/execution/engine/tools/platform/resource-invocation/__tests__/edge-cases.test.ts +0 -507
- package/src/execution/engine/tools/platform/resource-invocation/__tests__/resource-invocation-service.test.ts +0 -500
- package/src/execution/engine/tools/platform/resource-invocation/__tests__/tool.test.ts +0 -555
- package/src/execution/engine/tools/platform/resource-invocation/dynamic-tool.ts +0 -94
- package/src/execution/engine/tools/platform/resource-invocation/index.ts +0 -14
- package/src/execution/engine/tools/platform/resource-invocation/resource-invocation-service.ts +0 -147
- package/src/execution/engine/tools/platform/resource-invocation/tool.ts +0 -115
- package/src/execution/engine/tools/platform/resource-invocation/types.ts +0 -31
|
@@ -1,555 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
-
import { z } from 'zod'
|
|
3
|
-
import { createResourceInvocationTool } from '../tool'
|
|
4
|
-
import { createDynamicResourceInvocationTool } from '../dynamic-tool'
|
|
5
|
-
import { getToolServices } from '../../../registry'
|
|
6
|
-
import type { ResourceInvocationService } from '../resource-invocation-service'
|
|
7
|
-
import type { WorkflowDefinition } from '../../../../workflow/types'
|
|
8
|
-
import type { AgentDefinition } from '../../../../agent/core/types'
|
|
9
|
-
import type { ResourceInvocationResult } from '../types'
|
|
10
|
-
import { createMockExecutionContext } from '../../../../test-utils/mocks'
|
|
11
|
-
|
|
12
|
-
vi.mock('../../../registry', () => ({
|
|
13
|
-
getToolServices: vi.fn()
|
|
14
|
-
}))
|
|
15
|
-
|
|
16
|
-
describe('createResourceInvocationTool', () => {
|
|
17
|
-
const mockContext = createMockExecutionContext({
|
|
18
|
-
organizationId: 'org-123',
|
|
19
|
-
organizationName: 'test-org',
|
|
20
|
-
executionId: 'exec-parent',
|
|
21
|
-
resourceId: 'parent-agent'
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
const mockWorkflowDefinition: WorkflowDefinition = {
|
|
25
|
-
config: {
|
|
26
|
-
resourceId: 'validation-workflow',
|
|
27
|
-
name: 'Validation Workflow',
|
|
28
|
-
description: 'Validates lead data',
|
|
29
|
-
version: '1.0.0',
|
|
30
|
-
type: 'workflow',
|
|
31
|
-
status: 'dev'
|
|
32
|
-
},
|
|
33
|
-
contract: {
|
|
34
|
-
inputSchema: z.object({
|
|
35
|
-
email: z.string().email(),
|
|
36
|
-
company: z.string()
|
|
37
|
-
}),
|
|
38
|
-
outputSchema: z.object({
|
|
39
|
-
valid: z.boolean(),
|
|
40
|
-
errors: z.array(z.string())
|
|
41
|
-
})
|
|
42
|
-
},
|
|
43
|
-
entryPoint: 'validate',
|
|
44
|
-
steps: {}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const mockAgentDefinition: AgentDefinition = {
|
|
48
|
-
config: {
|
|
49
|
-
resourceId: 'research-agent',
|
|
50
|
-
name: 'Research Agent',
|
|
51
|
-
description: 'Researches companies',
|
|
52
|
-
version: '1.0.0',
|
|
53
|
-
type: 'agent',
|
|
54
|
-
status: 'dev'
|
|
55
|
-
},
|
|
56
|
-
contract: {
|
|
57
|
-
inputSchema: z.object({
|
|
58
|
-
query: z.string()
|
|
59
|
-
}),
|
|
60
|
-
outputSchema: z.object({
|
|
61
|
-
findings: z.string()
|
|
62
|
-
})
|
|
63
|
-
},
|
|
64
|
-
modelConfig: {
|
|
65
|
-
provider: 'openai',
|
|
66
|
-
model: 'gpt-4o',
|
|
67
|
-
temperature: 0.7
|
|
68
|
-
},
|
|
69
|
-
systemPrompt: 'You are a research agent',
|
|
70
|
-
tools: []
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
let mockService: ResourceInvocationService
|
|
74
|
-
|
|
75
|
-
beforeEach(() => {
|
|
76
|
-
mockService = {
|
|
77
|
-
executeSync: vi.fn()
|
|
78
|
-
} as unknown as ResourceInvocationService
|
|
79
|
-
|
|
80
|
-
vi.mocked(getToolServices).mockReturnValue({
|
|
81
|
-
commandQueueService: null,
|
|
82
|
-
taskSchedulerService: null,
|
|
83
|
-
notificationsService: null,
|
|
84
|
-
credentialsService: null,
|
|
85
|
-
integrationService: null,
|
|
86
|
-
resourceInvocationService: mockService
|
|
87
|
-
})
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
describe('tool creation', () => {
|
|
91
|
-
it('derives schemas from resource contract', () => {
|
|
92
|
-
const tool = createResourceInvocationTool({
|
|
93
|
-
name: 'invoke_lead_validation',
|
|
94
|
-
description: 'Validate lead data',
|
|
95
|
-
resource: mockWorkflowDefinition
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
expect(tool.name).toBe('invoke_lead_validation')
|
|
99
|
-
expect(tool.description).toBe('Validate lead data')
|
|
100
|
-
expect(tool.inputSchema).toBe(mockWorkflowDefinition.contract.inputSchema)
|
|
101
|
-
expect(tool.outputSchema).toBe(mockWorkflowDefinition.contract.outputSchema)
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
it('uses z.null() for outputSchema when resource has no outputSchema', () => {
|
|
105
|
-
const workflowWithoutOutput: WorkflowDefinition = {
|
|
106
|
-
...mockWorkflowDefinition,
|
|
107
|
-
contract: {
|
|
108
|
-
inputSchema: z.object({ data: z.string() }),
|
|
109
|
-
outputSchema: null
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const tool = createResourceInvocationTool({
|
|
114
|
-
name: 'fire_and_forget',
|
|
115
|
-
description: 'Fire and forget workflow',
|
|
116
|
-
resource: workflowWithoutOutput
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
expect(tool.outputSchema).toBeInstanceOf(z.ZodNull)
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
it('creates tool for agent resource', () => {
|
|
123
|
-
const tool = createResourceInvocationTool({
|
|
124
|
-
name: 'invoke_research',
|
|
125
|
-
description: 'Research company',
|
|
126
|
-
resource: mockAgentDefinition
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
expect(tool.name).toBe('invoke_research')
|
|
130
|
-
expect(tool.inputSchema).toBe(mockAgentDefinition.contract.inputSchema)
|
|
131
|
-
expect(tool.outputSchema).toBe(mockAgentDefinition.contract.outputSchema)
|
|
132
|
-
})
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
describe('tool execution', () => {
|
|
136
|
-
it('executes workflow and returns output', async () => {
|
|
137
|
-
const tool = createResourceInvocationTool({
|
|
138
|
-
name: 'invoke_lead_validation',
|
|
139
|
-
description: 'Validate lead data',
|
|
140
|
-
resource: mockWorkflowDefinition
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
const mockResult: ResourceInvocationResult = {
|
|
144
|
-
success: true,
|
|
145
|
-
output: { valid: true, errors: [] },
|
|
146
|
-
executionId: 'exec-child',
|
|
147
|
-
resourceType: 'workflow'
|
|
148
|
-
}
|
|
149
|
-
vi.mocked(mockService.executeSync).mockResolvedValue(mockResult)
|
|
150
|
-
|
|
151
|
-
const result = await tool.execute({
|
|
152
|
-
input: { email: 'test@test.com', company: 'ACME Corp' },
|
|
153
|
-
executionContext: mockContext
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
expect(result).toEqual({ valid: true, errors: [] })
|
|
157
|
-
expect(mockService.executeSync).toHaveBeenCalledWith(
|
|
158
|
-
'validation-workflow',
|
|
159
|
-
{ email: 'test@test.com', company: 'ACME Corp' },
|
|
160
|
-
'test-org',
|
|
161
|
-
mockContext
|
|
162
|
-
)
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
it('executes agent and returns output', async () => {
|
|
166
|
-
const tool = createResourceInvocationTool({
|
|
167
|
-
name: 'invoke_research',
|
|
168
|
-
description: 'Research company',
|
|
169
|
-
resource: mockAgentDefinition
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
const mockResult: ResourceInvocationResult = {
|
|
173
|
-
success: true,
|
|
174
|
-
output: { findings: 'Research results...' },
|
|
175
|
-
executionId: 'exec-research',
|
|
176
|
-
resourceType: 'agent'
|
|
177
|
-
}
|
|
178
|
-
vi.mocked(mockService.executeSync).mockResolvedValue(mockResult)
|
|
179
|
-
|
|
180
|
-
const result = await tool.execute({
|
|
181
|
-
input: { query: 'ACME Corp' },
|
|
182
|
-
executionContext: mockContext
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
expect(result).toEqual({ findings: 'Research results...' })
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
it('returns null for fire-and-forget resources', async () => {
|
|
189
|
-
const fireAndForgetWorkflow: WorkflowDefinition = {
|
|
190
|
-
...mockWorkflowDefinition,
|
|
191
|
-
contract: {
|
|
192
|
-
inputSchema: z.object({ data: z.string() }),
|
|
193
|
-
outputSchema: null
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const tool = createResourceInvocationTool({
|
|
198
|
-
name: 'fire_and_forget',
|
|
199
|
-
description: 'Fire and forget',
|
|
200
|
-
resource: fireAndForgetWorkflow
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
const mockResult: ResourceInvocationResult = {
|
|
204
|
-
success: true,
|
|
205
|
-
output: null,
|
|
206
|
-
executionId: 'exec-fire',
|
|
207
|
-
resourceType: 'workflow'
|
|
208
|
-
}
|
|
209
|
-
vi.mocked(mockService.executeSync).mockResolvedValue(mockResult)
|
|
210
|
-
|
|
211
|
-
const result = await tool.execute({
|
|
212
|
-
input: { data: 'test' },
|
|
213
|
-
executionContext: mockContext
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
expect(result).toBeNull()
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
it('throws error when execution fails', async () => {
|
|
220
|
-
const tool = createResourceInvocationTool({
|
|
221
|
-
name: 'invoke_lead_validation',
|
|
222
|
-
description: 'Validate lead data',
|
|
223
|
-
resource: mockWorkflowDefinition
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
const mockResult: ResourceInvocationResult = {
|
|
227
|
-
success: false,
|
|
228
|
-
output: null,
|
|
229
|
-
executionId: '',
|
|
230
|
-
resourceType: 'workflow',
|
|
231
|
-
error: 'Validation failed: missing required fields'
|
|
232
|
-
}
|
|
233
|
-
vi.mocked(mockService.executeSync).mockResolvedValue(mockResult)
|
|
234
|
-
|
|
235
|
-
await expect(
|
|
236
|
-
tool.execute({
|
|
237
|
-
input: { email: 'test@test.com', company: 'ACME' },
|
|
238
|
-
executionContext: mockContext
|
|
239
|
-
})
|
|
240
|
-
).rejects.toThrow('Validation failed: missing required fields')
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
it('throws error with resourceType when no error message provided', async () => {
|
|
244
|
-
const tool = createResourceInvocationTool({
|
|
245
|
-
name: 'invoke_research',
|
|
246
|
-
description: 'Research company',
|
|
247
|
-
resource: mockAgentDefinition
|
|
248
|
-
})
|
|
249
|
-
|
|
250
|
-
const mockResult: ResourceInvocationResult = {
|
|
251
|
-
success: false,
|
|
252
|
-
output: null,
|
|
253
|
-
executionId: '',
|
|
254
|
-
resourceType: 'agent'
|
|
255
|
-
// No error message
|
|
256
|
-
}
|
|
257
|
-
vi.mocked(mockService.executeSync).mockResolvedValue(mockResult)
|
|
258
|
-
|
|
259
|
-
await expect(
|
|
260
|
-
tool.execute({
|
|
261
|
-
input: { query: 'test' },
|
|
262
|
-
executionContext: mockContext
|
|
263
|
-
})
|
|
264
|
-
).rejects.toThrow('agent execution failed')
|
|
265
|
-
})
|
|
266
|
-
})
|
|
267
|
-
|
|
268
|
-
describe('service availability', () => {
|
|
269
|
-
it('throws if service not initialized', async () => {
|
|
270
|
-
vi.mocked(getToolServices).mockReturnValue({
|
|
271
|
-
commandQueueService: null,
|
|
272
|
-
taskSchedulerService: null,
|
|
273
|
-
notificationsService: null,
|
|
274
|
-
credentialsService: null,
|
|
275
|
-
integrationService: null,
|
|
276
|
-
resourceInvocationService: undefined
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
const tool = createResourceInvocationTool({
|
|
280
|
-
name: 'invoke_lead_validation',
|
|
281
|
-
description: 'Validate lead data',
|
|
282
|
-
resource: mockWorkflowDefinition
|
|
283
|
-
})
|
|
284
|
-
|
|
285
|
-
await expect(
|
|
286
|
-
tool.execute({
|
|
287
|
-
input: { email: 'test@test.com', company: 'ACME' },
|
|
288
|
-
executionContext: mockContext
|
|
289
|
-
})
|
|
290
|
-
).rejects.toThrow('ResourceInvocationService is unavailable')
|
|
291
|
-
})
|
|
292
|
-
|
|
293
|
-
it('throws if context missing', async () => {
|
|
294
|
-
const tool = createResourceInvocationTool({
|
|
295
|
-
name: 'invoke_lead_validation',
|
|
296
|
-
description: 'Validate lead data',
|
|
297
|
-
resource: mockWorkflowDefinition
|
|
298
|
-
})
|
|
299
|
-
|
|
300
|
-
await expect(
|
|
301
|
-
tool.execute({
|
|
302
|
-
input: { email: 'test@test.com', company: 'ACME' }
|
|
303
|
-
// No executionContext
|
|
304
|
-
})
|
|
305
|
-
).rejects.toThrow('ExecutionContext is unavailable')
|
|
306
|
-
})
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
describe('organization context', () => {
|
|
310
|
-
it('uses organizationName from executionContext', async () => {
|
|
311
|
-
const tool = createResourceInvocationTool({
|
|
312
|
-
name: 'invoke_lead_validation',
|
|
313
|
-
description: 'Validate lead data',
|
|
314
|
-
resource: mockWorkflowDefinition
|
|
315
|
-
})
|
|
316
|
-
|
|
317
|
-
const contextWithOrg = createMockExecutionContext({
|
|
318
|
-
organizationName: 'acme-corp'
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
const mockResult: ResourceInvocationResult = {
|
|
322
|
-
success: true,
|
|
323
|
-
output: { valid: true, errors: [] },
|
|
324
|
-
executionId: 'exec-org',
|
|
325
|
-
resourceType: 'workflow'
|
|
326
|
-
}
|
|
327
|
-
vi.mocked(mockService.executeSync).mockResolvedValue(mockResult)
|
|
328
|
-
|
|
329
|
-
await tool.execute({
|
|
330
|
-
input: { email: 'test@test.com', company: 'ACME' },
|
|
331
|
-
executionContext: contextWithOrg
|
|
332
|
-
})
|
|
333
|
-
|
|
334
|
-
expect(mockService.executeSync).toHaveBeenCalledWith(
|
|
335
|
-
'validation-workflow',
|
|
336
|
-
expect.anything(),
|
|
337
|
-
'acme-corp',
|
|
338
|
-
contextWithOrg
|
|
339
|
-
)
|
|
340
|
-
})
|
|
341
|
-
})
|
|
342
|
-
})
|
|
343
|
-
|
|
344
|
-
describe('createDynamicResourceInvocationTool', () => {
|
|
345
|
-
const mockContext = createMockExecutionContext({
|
|
346
|
-
organizationId: 'org-123',
|
|
347
|
-
organizationName: 'test-org',
|
|
348
|
-
executionId: 'exec-parent'
|
|
349
|
-
})
|
|
350
|
-
|
|
351
|
-
let mockService: ResourceInvocationService
|
|
352
|
-
|
|
353
|
-
beforeEach(() => {
|
|
354
|
-
mockService = {
|
|
355
|
-
executeSync: vi.fn()
|
|
356
|
-
} as unknown as ResourceInvocationService
|
|
357
|
-
|
|
358
|
-
vi.mocked(getToolServices).mockReturnValue({
|
|
359
|
-
commandQueueService: null,
|
|
360
|
-
taskSchedulerService: null,
|
|
361
|
-
notificationsService: null,
|
|
362
|
-
credentialsService: null,
|
|
363
|
-
integrationService: null,
|
|
364
|
-
resourceInvocationService: mockService
|
|
365
|
-
})
|
|
366
|
-
})
|
|
367
|
-
|
|
368
|
-
describe('tool creation', () => {
|
|
369
|
-
it('creates tool with dynamic input schema', () => {
|
|
370
|
-
const tool = createDynamicResourceInvocationTool()
|
|
371
|
-
|
|
372
|
-
expect(tool.name).toBe('invoke_resource')
|
|
373
|
-
expect(tool.description).toContain('Invoke any resource')
|
|
374
|
-
expect(tool.inputSchema).toBeDefined()
|
|
375
|
-
expect(tool.outputSchema).toBeDefined()
|
|
376
|
-
})
|
|
377
|
-
})
|
|
378
|
-
|
|
379
|
-
describe('tool execution', () => {
|
|
380
|
-
it('returns full ResourceInvocationResult', async () => {
|
|
381
|
-
const tool = createDynamicResourceInvocationTool()
|
|
382
|
-
|
|
383
|
-
const mockResult: ResourceInvocationResult = {
|
|
384
|
-
success: true,
|
|
385
|
-
output: { data: 'result' },
|
|
386
|
-
executionId: 'exec-dynamic',
|
|
387
|
-
resourceType: 'workflow'
|
|
388
|
-
}
|
|
389
|
-
vi.mocked(mockService.executeSync).mockResolvedValue(mockResult)
|
|
390
|
-
|
|
391
|
-
const result = await tool.execute({
|
|
392
|
-
input: {
|
|
393
|
-
resourceId: 'validation-workflow',
|
|
394
|
-
input: { email: 'test@test.com' }
|
|
395
|
-
},
|
|
396
|
-
executionContext: mockContext
|
|
397
|
-
})
|
|
398
|
-
|
|
399
|
-
expect(result).toEqual(mockResult)
|
|
400
|
-
expect(mockService.executeSync).toHaveBeenCalledWith(
|
|
401
|
-
'validation-workflow',
|
|
402
|
-
{ email: 'test@test.com' },
|
|
403
|
-
'test-org',
|
|
404
|
-
mockContext
|
|
405
|
-
)
|
|
406
|
-
})
|
|
407
|
-
|
|
408
|
-
it('invokes agent resource dynamically', async () => {
|
|
409
|
-
const tool = createDynamicResourceInvocationTool()
|
|
410
|
-
|
|
411
|
-
const mockResult: ResourceInvocationResult = {
|
|
412
|
-
success: true,
|
|
413
|
-
output: { findings: 'results' },
|
|
414
|
-
executionId: 'exec-agent',
|
|
415
|
-
resourceType: 'agent'
|
|
416
|
-
}
|
|
417
|
-
vi.mocked(mockService.executeSync).mockResolvedValue(mockResult)
|
|
418
|
-
|
|
419
|
-
const result = await tool.execute({
|
|
420
|
-
input: {
|
|
421
|
-
resourceId: 'research-agent',
|
|
422
|
-
input: { query: 'test query' }
|
|
423
|
-
},
|
|
424
|
-
executionContext: mockContext
|
|
425
|
-
})
|
|
426
|
-
|
|
427
|
-
expect(result).toEqual(mockResult)
|
|
428
|
-
expect((result as ResourceInvocationResult).resourceType).toBe('agent')
|
|
429
|
-
})
|
|
430
|
-
|
|
431
|
-
it('returns complete result with metadata', async () => {
|
|
432
|
-
const tool = createDynamicResourceInvocationTool()
|
|
433
|
-
|
|
434
|
-
const mockResult: ResourceInvocationResult = {
|
|
435
|
-
success: true,
|
|
436
|
-
output: { value: 'test' },
|
|
437
|
-
executionId: 'exec-metadata',
|
|
438
|
-
resourceType: 'workflow'
|
|
439
|
-
}
|
|
440
|
-
vi.mocked(mockService.executeSync).mockResolvedValue(mockResult)
|
|
441
|
-
|
|
442
|
-
const result = await tool.execute({
|
|
443
|
-
input: {
|
|
444
|
-
resourceId: 'any-resource',
|
|
445
|
-
input: { key: 'value' }
|
|
446
|
-
},
|
|
447
|
-
executionContext: mockContext
|
|
448
|
-
})
|
|
449
|
-
|
|
450
|
-
// Verify all metadata fields present
|
|
451
|
-
expect(result).toHaveProperty('success')
|
|
452
|
-
expect(result).toHaveProperty('output')
|
|
453
|
-
expect(result).toHaveProperty('executionId')
|
|
454
|
-
expect(result).toHaveProperty('resourceType')
|
|
455
|
-
})
|
|
456
|
-
|
|
457
|
-
it('returns failed result without throwing', async () => {
|
|
458
|
-
const tool = createDynamicResourceInvocationTool()
|
|
459
|
-
|
|
460
|
-
const mockResult: ResourceInvocationResult = {
|
|
461
|
-
success: false,
|
|
462
|
-
output: null,
|
|
463
|
-
executionId: '',
|
|
464
|
-
resourceType: 'workflow',
|
|
465
|
-
error: 'Resource not found'
|
|
466
|
-
}
|
|
467
|
-
vi.mocked(mockService.executeSync).mockResolvedValue(mockResult)
|
|
468
|
-
|
|
469
|
-
const result = await tool.execute({
|
|
470
|
-
input: {
|
|
471
|
-
resourceId: 'non-existent',
|
|
472
|
-
input: {}
|
|
473
|
-
},
|
|
474
|
-
executionContext: mockContext
|
|
475
|
-
})
|
|
476
|
-
|
|
477
|
-
// Dynamic tool returns full result, doesn't throw
|
|
478
|
-
expect(result).toEqual(mockResult)
|
|
479
|
-
expect((result as ResourceInvocationResult).success).toBe(false)
|
|
480
|
-
expect((result as ResourceInvocationResult).error).toBe('Resource not found')
|
|
481
|
-
})
|
|
482
|
-
})
|
|
483
|
-
|
|
484
|
-
describe('service availability', () => {
|
|
485
|
-
it('throws if service not initialized', async () => {
|
|
486
|
-
vi.mocked(getToolServices).mockReturnValue({
|
|
487
|
-
commandQueueService: null,
|
|
488
|
-
taskSchedulerService: null,
|
|
489
|
-
notificationsService: null,
|
|
490
|
-
credentialsService: null,
|
|
491
|
-
integrationService: null,
|
|
492
|
-
resourceInvocationService: undefined
|
|
493
|
-
})
|
|
494
|
-
|
|
495
|
-
const tool = createDynamicResourceInvocationTool()
|
|
496
|
-
|
|
497
|
-
await expect(
|
|
498
|
-
tool.execute({
|
|
499
|
-
input: {
|
|
500
|
-
resourceId: 'test-resource',
|
|
501
|
-
input: {}
|
|
502
|
-
},
|
|
503
|
-
executionContext: mockContext
|
|
504
|
-
})
|
|
505
|
-
).rejects.toThrow('ResourceInvocationService is unavailable')
|
|
506
|
-
})
|
|
507
|
-
|
|
508
|
-
it('throws if context missing', async () => {
|
|
509
|
-
const tool = createDynamicResourceInvocationTool()
|
|
510
|
-
|
|
511
|
-
await expect(
|
|
512
|
-
tool.execute({
|
|
513
|
-
input: {
|
|
514
|
-
resourceId: 'test-resource',
|
|
515
|
-
input: {}
|
|
516
|
-
}
|
|
517
|
-
// No executionContext
|
|
518
|
-
})
|
|
519
|
-
).rejects.toThrow('ExecutionContext is unavailable')
|
|
520
|
-
})
|
|
521
|
-
})
|
|
522
|
-
|
|
523
|
-
describe('organization context', () => {
|
|
524
|
-
it('uses organizationName from executionContext', async () => {
|
|
525
|
-
const tool = createDynamicResourceInvocationTool()
|
|
526
|
-
|
|
527
|
-
const contextWithOrg = createMockExecutionContext({
|
|
528
|
-
organizationName: 'different-org'
|
|
529
|
-
})
|
|
530
|
-
|
|
531
|
-
const mockResult: ResourceInvocationResult = {
|
|
532
|
-
success: true,
|
|
533
|
-
output: { data: 'test' },
|
|
534
|
-
executionId: 'exec-ctx',
|
|
535
|
-
resourceType: 'workflow'
|
|
536
|
-
}
|
|
537
|
-
vi.mocked(mockService.executeSync).mockResolvedValue(mockResult)
|
|
538
|
-
|
|
539
|
-
await tool.execute({
|
|
540
|
-
input: {
|
|
541
|
-
resourceId: 'some-resource',
|
|
542
|
-
input: { key: 'value' }
|
|
543
|
-
},
|
|
544
|
-
executionContext: contextWithOrg
|
|
545
|
-
})
|
|
546
|
-
|
|
547
|
-
expect(mockService.executeSync).toHaveBeenCalledWith(
|
|
548
|
-
'some-resource',
|
|
549
|
-
{ key: 'value' },
|
|
550
|
-
'different-org',
|
|
551
|
-
contextWithOrg
|
|
552
|
-
)
|
|
553
|
-
})
|
|
554
|
-
})
|
|
555
|
-
})
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dynamic Resource Invocation Tool Factory
|
|
3
|
-
*
|
|
4
|
-
* Creates a generic tool that can invoke any resource by ID at runtime.
|
|
5
|
-
* Useful for agents that need to select which resource to invoke dynamically.
|
|
6
|
-
*
|
|
7
|
-
* @module resource-invocation/dynamic-tool
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { z } from 'zod'
|
|
11
|
-
import { serviceUnavailable } from '../../types'
|
|
12
|
-
import type { Tool, ToolExecutionOptions } from '../../types'
|
|
13
|
-
import { getToolServices } from '../../registry'
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Input schema for dynamic resource invocation
|
|
17
|
-
* Takes resourceId as input parameter along with the input payload
|
|
18
|
-
*/
|
|
19
|
-
export const dynamicResourceInvocationSchema = z.object({
|
|
20
|
-
resourceId: z.string().describe('ID of resource (agent or workflow) to invoke'),
|
|
21
|
-
input: z.record(z.string(), z.unknown()).describe('Input payload for resource')
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Output schema for dynamic resource invocation
|
|
26
|
-
* Returns complete ResourceInvocationResult including metadata
|
|
27
|
-
*/
|
|
28
|
-
export const dynamicResourceInvocationOutputSchema = z.object({
|
|
29
|
-
success: z.boolean().describe('Whether resource execution succeeded'),
|
|
30
|
-
output: z.unknown().describe('Output from resource (or null if no outputSchema)'),
|
|
31
|
-
executionId: z.string().describe('Execution ID for tracking'),
|
|
32
|
-
resourceType: z.enum(['agent', 'workflow']).describe('Type of resource executed')
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Create a dynamic resource invocation tool
|
|
37
|
-
*
|
|
38
|
-
* Unlike createResourceInvocationTool() which binds to a specific resource at build time,
|
|
39
|
-
* this factory creates a generic tool that accepts resourceId as an input parameter.
|
|
40
|
-
*
|
|
41
|
-
* The tool returns the full ResourceInvocationResult (not just output) to provide
|
|
42
|
-
* visibility into execution metadata.
|
|
43
|
-
*
|
|
44
|
-
* @returns Tool that can invoke any resource by ID at runtime
|
|
45
|
-
*
|
|
46
|
-
* @example
|
|
47
|
-
* // Add to agent tools for dynamic resource selection
|
|
48
|
-
* const agent: AgentDefinition = {
|
|
49
|
-
* tools: [
|
|
50
|
-
* createDynamicResourceInvocationTool(),
|
|
51
|
-
* // ... other tools
|
|
52
|
-
* ]
|
|
53
|
-
* }
|
|
54
|
-
*
|
|
55
|
-
* // Agent can now invoke any resource dynamically:
|
|
56
|
-
* // - invoke_resource({ resourceId: 'lead-validation', input: { email: '...' } })
|
|
57
|
-
* // - invoke_resource({ resourceId: 'company-research', input: { query: '...' } })
|
|
58
|
-
*/
|
|
59
|
-
export function createDynamicResourceInvocationTool(): Tool {
|
|
60
|
-
return {
|
|
61
|
-
name: 'invoke_resource',
|
|
62
|
-
description: 'Invoke any resource (agent or workflow) by ID with custom input',
|
|
63
|
-
inputSchema: dynamicResourceInvocationSchema,
|
|
64
|
-
outputSchema: dynamicResourceInvocationOutputSchema,
|
|
65
|
-
|
|
66
|
-
async execute(options: ToolExecutionOptions): Promise<unknown> {
|
|
67
|
-
// Get resource invocation service from global registry
|
|
68
|
-
const { resourceInvocationService } = getToolServices()
|
|
69
|
-
|
|
70
|
-
if (!resourceInvocationService) {
|
|
71
|
-
throw serviceUnavailable('ResourceInvocationService')
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (!options.executionContext) {
|
|
75
|
-
throw serviceUnavailable('ExecutionContext', { tool: 'invoke_resource', reason: 'organizationId required for multi-tenant isolation' })
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Extract resourceId and input from tool input
|
|
79
|
-
const { resourceId, input } = options.input as z.infer<typeof dynamicResourceInvocationSchema>
|
|
80
|
-
|
|
81
|
-
// Execute resource and return complete result
|
|
82
|
-
// organizationName is guaranteed to be present in ExecutionContext (extends ExecutionMetadata)
|
|
83
|
-
const result = await resourceInvocationService.executeSync(
|
|
84
|
-
resourceId,
|
|
85
|
-
input,
|
|
86
|
-
options.executionContext.organizationName,
|
|
87
|
-
options.executionContext
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
// Return complete result (not just output) for visibility into execution metadata
|
|
91
|
-
return result
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resource Invocation Platform Tool
|
|
3
|
-
*
|
|
4
|
-
* Enables agents to invoke other resources (agents and workflows) as tools.
|
|
5
|
-
* Supports nested execution with parent-child tracking and multi-tenancy isolation.
|
|
6
|
-
*
|
|
7
|
-
* @module resource-invocation
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export { ResourceInvocationService, createResourceInvocationService } from './resource-invocation-service'
|
|
11
|
-
export type { ResourceExecutionRequest, ResourceInvocationResult } from './types'
|
|
12
|
-
export { createResourceInvocationTool } from './tool'
|
|
13
|
-
export type { ResourceInvocationToolConfig, ResourceDefinition } from './tool'
|
|
14
|
-
export { createDynamicResourceInvocationTool, dynamicResourceInvocationSchema, dynamicResourceInvocationOutputSchema } from './dynamic-tool'
|