/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import express from 'express'; import type { AgentCard } from '@a2a-js/sdk'; import type { TaskStore } from '@a2a-js/sdk/server'; import { DefaultRequestHandler, InMemoryTaskStore } from '@a2a-js/sdk/server'; import { A2AExpressApp } from '@a2a-js/sdk/server/express'; // Import server components import { v4 as uuidv4 } from 'uuid'; import { logger } from '../utils/logger.js'; import type { AgentSettings } from '../types.js'; import { GCSTaskStore, NoOpTaskStore } from '../persistence/gcs.js'; import { CoderAgentExecutor } from '../agent/executor.js'; import { requestStorage } from './requestStorage.js'; const coderAgentCard: AgentCard = { name: 'Gemini SDLC Agent', description: 'An agent that generates code based on natural language instructions and streams file outputs.', url: 'http://localhost:41242/', provider: { organization: 'Google', url: 'https://google.com', }, protocolVersion: '0.3.0', version: '0.0.2', // Incremented version capabilities: { streaming: true, pushNotifications: false, stateTransitionHistory: true, }, securitySchemes: undefined, security: undefined, defaultInputModes: ['text'], defaultOutputModes: ['text'], skills: [ { id: 'code_generation', name: 'Code Generation', description: 'Generates code snippets or complete files based on user requests, streaming the results.', tags: ['code', 'development', 'programming'], examples: [ 'Write a python function to calculate fibonacci numbers.', 'Create an HTML file with a basic button that alerts "Hello!" when clicked.', ], inputModes: ['text'], outputModes: ['text'], }, ], supportsAuthenticatedExtendedCard: false, }; export function updateCoderAgentCardUrl(port: number) { coderAgentCard.url = `http://localhost:${port}/`; } export async function createApp() { try { // loadEnvironment() is called within getConfig now const bucketName = process.env['GCS_BUCKET_NAME']; let taskStoreForExecutor: TaskStore; let taskStoreForHandler: TaskStore; if (bucketName) { logger.info(`Using GCSTaskStore with bucket: ${bucketName}`); const gcsTaskStore = new GCSTaskStore(bucketName); taskStoreForExecutor = gcsTaskStore; taskStoreForHandler = new NoOpTaskStore(gcsTaskStore); } else { logger.info('Using InMemoryTaskStore'); const inMemoryTaskStore = new InMemoryTaskStore(); taskStoreForExecutor = inMemoryTaskStore; taskStoreForHandler = inMemoryTaskStore; } const agentExecutor = new CoderAgentExecutor(taskStoreForExecutor); const requestHandler = new DefaultRequestHandler( coderAgentCard, taskStoreForHandler, agentExecutor, ); let expressApp = express(); expressApp.use((req, res, next) => { requestStorage.run({ req }, next); }); const appBuilder = new A2AExpressApp(requestHandler); expressApp = appBuilder.setupRoutes(expressApp, ''); expressApp.use(express.json()); expressApp.post('/tasks', async (req, res) => { try { const taskId = uuidv4(); const agentSettings = req.body.agentSettings as | AgentSettings | undefined; const contextId = req.body.contextId || uuidv4(); const wrapper = await agentExecutor.createTask( taskId, contextId, agentSettings, ); await taskStoreForExecutor.save(wrapper.toSDKTask()); res.status(201).json(wrapper.id); } catch (error) { logger.error('[CoreAgent] Error creating task:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown error creating task'; res.status(500).send({ 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)) { res.status(501).send({ error: 'Listing all task metadata is only supported when using InMemoryTaskStore.', }); } try { const wrappers = agentExecutor.getAllTasks(); if (wrappers && wrappers.length > 0) { const tasksMetadata = await Promise.all( wrappers.map((wrapper) => wrapper.task.getMetadata()), ); res.status(200).json(tasksMetadata); } else { res.status(204).send(); } } catch (error) { logger.error('[CoreAgent] Error getting all task metadata:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown error getting task metadata'; res.status(500).send({ error: errorMessage }); } }); expressApp.get('/tasks/:taskId/metadata', async (req, res) => { const taskId = req.params.taskId; let wrapper = agentExecutor.getTask(taskId); if (!wrapper) { const sdkTask = await taskStoreForExecutor.load(taskId); if (sdkTask) { wrapper = await agentExecutor.reconstruct(sdkTask); } } if (!wrapper) { res.status(404).send({ error: 'Task not found' }); return; } res.json({ metadata: await wrapper.task.getMetadata() }); }); return expressApp; } catch (error) { logger.error('[CoreAgent] Error during startup:', error); process.exit(1); } } export async function main() { try { const expressApp = await createApp(); const port = process.env['CODER_AGENT_PORT'] || 0; const server = expressApp.listen(port, () => { const address = server.address(); let actualPort; if (process.env['CODER_AGENT_PORT']) { actualPort = process.env['CODER_AGENT_PORT']; } else if (address && typeof address !== 'string') { actualPort = address.port; } else { throw new Error('[Core Agent] Could not find port number.'); } updateCoderAgentCardUrl(Number(actualPort)); logger.info( `[CoreAgent] Agent Server started on http://localhost:${actualPort}`, ); logger.info( `[CoreAgent] Agent Card: http://localhost:${actualPort}/.well-known/agent-card.json`, ); logger.info('[CoreAgent] Press Ctrl+C to stop the server'); }); } catch (error) { logger.error('[CoreAgent] Error during startup:', error); process.exit(1); } }