mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-26 13:04:49 -07:00
Adds executeCommand endpoint with support for /extensions list (#11515)
This commit is contained in:
@@ -65,6 +65,8 @@ let config: Config;
|
||||
const getToolRegistrySpy = vi.fn().mockReturnValue(ApprovalMode.DEFAULT);
|
||||
const getApprovalModeSpy = vi.fn();
|
||||
const getShellExecutionConfigSpy = vi.fn();
|
||||
const getExtensionsSpy = vi.fn();
|
||||
|
||||
vi.mock('../config/config.js', async () => {
|
||||
const actual = await vi.importActual('../config/config.js');
|
||||
return {
|
||||
@@ -74,6 +76,7 @@ vi.mock('../config/config.js', async () => {
|
||||
getToolRegistry: getToolRegistrySpy,
|
||||
getApprovalMode: getApprovalModeSpy,
|
||||
getShellExecutionConfig: getShellExecutionConfigSpy,
|
||||
getExtensions: getExtensionsSpy,
|
||||
});
|
||||
config = mockConfig as Config;
|
||||
return config;
|
||||
@@ -652,4 +655,62 @@ describe('E2E Tests', () => {
|
||||
expect(thoughtEvent.kind).toBe('status-update');
|
||||
expect(thoughtEvent.metadata?.['traceId']).toBe(traceId);
|
||||
});
|
||||
|
||||
describe('/executeCommand', () => {
|
||||
const mockExtensions = [{ name: 'test-extension', version: '0.0.1' }];
|
||||
|
||||
beforeEach(() => {
|
||||
getExtensionsSpy.mockReturnValue(mockExtensions);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
getExtensionsSpy.mockClear();
|
||||
});
|
||||
|
||||
it('should return extensions for valid command', async () => {
|
||||
const agent = request.agent(app);
|
||||
const res = await agent
|
||||
.post('/executeCommand')
|
||||
.send({ command: 'extensions list', args: [] })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
expect(res.body).toEqual(mockExtensions);
|
||||
expect(getExtensionsSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return 404 for invalid command', async () => {
|
||||
const agent = request.agent(app);
|
||||
const res = await agent
|
||||
.post('/executeCommand')
|
||||
.send({ command: 'invalid command' })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(404);
|
||||
|
||||
expect(res.body.error).toBe('Command not found: invalid command');
|
||||
expect(getExtensionsSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return 400 for missing command', async () => {
|
||||
const agent = request.agent(app);
|
||||
await agent
|
||||
.post('/executeCommand')
|
||||
.send({ args: [] })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
expect(getExtensionsSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return 400 if args is not an array', async () => {
|
||||
const agent = request.agent(app);
|
||||
const res = await agent
|
||||
.post('/executeCommand')
|
||||
.send({ command: 'extensions.list', args: 'not-an-array' })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
|
||||
expect(res.body.error).toBe('"args" field must be an array.');
|
||||
expect(getExtensionsSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,6 +16,10 @@ import type { AgentSettings } from '../types.js';
|
||||
import { GCSTaskStore, NoOpTaskStore } from '../persistence/gcs.js';
|
||||
import { CoderAgentExecutor } from '../agent/executor.js';
|
||||
import { requestStorage } from './requestStorage.js';
|
||||
import { loadConfig, loadEnvironment, setTargetDir } from '../config/config.js';
|
||||
import { loadSettings } from '../config/settings.js';
|
||||
import { loadExtensions } from '../config/extension.js';
|
||||
import { commandRegistry } from '../commands/command-registry.js';
|
||||
|
||||
const coderAgentCard: AgentCard = {
|
||||
name: 'Gemini SDLC Agent',
|
||||
@@ -61,6 +65,13 @@ export function updateCoderAgentCardUrl(port: number) {
|
||||
|
||||
export async function createApp() {
|
||||
try {
|
||||
// Load the server configuration once on startup.
|
||||
const workspaceRoot = setTargetDir(undefined);
|
||||
loadEnvironment();
|
||||
const settings = loadSettings(workspaceRoot);
|
||||
const extensions = loadExtensions(workspaceRoot);
|
||||
const config = await loadConfig(settings, extensions, 'a2a-server');
|
||||
|
||||
// loadEnvironment() is called within getConfig now
|
||||
const bucketName = process.env['GCS_BUCKET_NAME'];
|
||||
let taskStoreForExecutor: TaskStore;
|
||||
@@ -119,6 +130,38 @@ export async function createApp() {
|
||||
}
|
||||
});
|
||||
|
||||
expressApp.post('/executeCommand', async (req, res) => {
|
||||
try {
|
||||
const { command, args } = req.body;
|
||||
|
||||
if (typeof command !== 'string') {
|
||||
return res.status(400).json({ error: 'Invalid "command" field.' });
|
||||
}
|
||||
|
||||
if (args && !Array.isArray(args)) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: '"args" field must be an array.' });
|
||||
}
|
||||
|
||||
const commandToExecute = commandRegistry.get(command);
|
||||
|
||||
if (!commandToExecute) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ error: `Command not found: ${command}` });
|
||||
}
|
||||
|
||||
const result = await commandToExecute.execute(config, args ?? []);
|
||||
return res.status(200).json(result);
|
||||
} catch (e) {
|
||||
logger.error('Error executing /executeCommand:', e);
|
||||
const errorMessage =
|
||||
e instanceof Error ? e.message : 'Unknown error executing command';
|
||||
return res.status(500).json({ error: errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
expressApp.get('/tasks/metadata', async (req, res) => {
|
||||
// This endpoint is only meaningful if the task store is in-memory.
|
||||
if (!(taskStoreForExecutor instanceof InMemoryTaskStore)) {
|
||||
|
||||
Reference in New Issue
Block a user