()
| 365 | } |
| 366 | |
| 367 | export function createTrainingAgentRouter(): Router { |
| 368 | const router = Router(); |
| 369 | |
| 370 | // Start session cleanup |
| 371 | startSessionCleanup(); |
| 372 | |
| 373 | // Health check |
| 374 | router.get('/health', (_req: Request, res: Response) => { |
| 375 | res.json({ status: 'healthy', service: 'training-agent' }); |
| 376 | }); |
| 377 | |
| 378 | // JWKS for webhook-signature verification by buyers (RFC 7517). |
| 379 | // Public keys only — the emitter holds the private half. |
| 380 | router.get('/.well-known/jwks.json', (_req: Request, res: Response) => { |
| 381 | res.setHeader('Cache-Control', 'public, max-age=300'); |
| 382 | res.json(getPublicJwks()); |
| 383 | }); |
| 384 | |
| 385 | // adagents.json discovery |
| 386 | router.get('/.well-known/adagents.json', (req: Request, res: Response) => { |
| 387 | const baseUrl = getBaseUrl(req); |
| 388 | // req.baseUrl is the mount path (e.g. '/api/training-agent') — empty when |
| 389 | // served via host-based routing at root |
| 390 | const agentUrl = `${baseUrl}${req.baseUrl}`; |
| 391 | |
| 392 | res.json({ |
| 393 | $schema: '/schemas/adagents.json', |
| 394 | contact: { |
| 395 | name: 'AdCP Training Agent', |
| 396 | url: 'https://adcontextprotocol.org', |
| 397 | }, |
| 398 | authorized_agents: [{ |
| 399 | url: `${agentUrl}/mcp`, |
| 400 | authorized_for: 'AdCP training, testing, and certification (sandbox)', |
| 401 | authorization_type: 'inline_properties', |
| 402 | properties: PUBLISHERS.flatMap(pub => |
| 403 | pub.properties.map(prop => ({ |
| 404 | identifier_type: prop.identifierType, |
| 405 | identifier_value: prop.identifierValue, |
| 406 | name: prop.name, |
| 407 | supported_channels: prop.channels, |
| 408 | tags: prop.tags, |
| 409 | })), |
| 410 | ), |
| 411 | }], |
| 412 | signals: SIGNAL_PROVIDERS.flatMap(provider => |
| 413 | provider.signals.map(signal => ({ |
| 414 | id: signal.signalAgentSegmentId, |
| 415 | name: signal.name, |
| 416 | description: signal.description, |
| 417 | value_type: signal.valueType, |
| 418 | tags: signal.tags, |
| 419 | ...(signal.categories && { allowed_values: signal.categories }), |
| 420 | ...(signal.range && { range: signal.range }), |
| 421 | })), |
| 422 | ), |
| 423 | signal_tags: { |
| 424 | automotive: { name: 'Automotive signals', description: 'Vehicle ownership, purchase intent, and service signals' }, |
no test coverage detected