UNPKG

@adonis-agora/agent

Version:

Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.

106 lines • 4.04 kB
/** * The slice of A2A 1.0 (HTTP+JSON binding) this surface speaks: the message shapes, the AIP-193 error * envelope, and the request validation PACT §4.1 asks for. Hand-rolled rather than schema-validated — * `zod` is an optional peer, and the rules are few and exact. */ export const A2A_CONTENT_TYPE = 'application/a2a+json'; /** The A2A error reasons PACT §6 uses, with their HTTP status and google.rpc status. */ const REASONS = { INVALID_PARAMS: { httpStatus: 400, status: 'INVALID_ARGUMENT' }, CONTENT_TYPE_NOT_SUPPORTED: { httpStatus: 400, status: 'INVALID_ARGUMENT' }, UNSUPPORTED_OPERATION: { httpStatus: 400, status: 'FAILED_PRECONDITION' }, PUSH_NOTIFICATION_NOT_SUPPORTED: { httpStatus: 400, status: 'FAILED_PRECONDITION' }, TASK_NOT_FOUND: { httpStatus: 404, status: 'NOT_FOUND' }, INTERNAL: { httpStatus: 500, status: 'INTERNAL' }, }; /** An A2A error — answered as the AIP-193 envelope, never as a bare status. */ export class A2aError extends Error { reason; constructor(reason, message) { super(message); this.reason = reason; } get httpStatus() { return REASONS[this.reason].httpStatus; } toJSON() { const { httpStatus, status } = REASONS[this.reason]; return { error: { code: httpStatus, status, message: this.message, details: [ { '@type': 'type.googleapis.com/google.rpc.ErrorInfo', reason: this.reason, domain: 'a2a-protocol.org', }, ], }, }; } } function isRecord(value) { return typeof value === 'object' && value !== null && !Array.isArray(value); } /** * Parse and validate a `SendMessageRequest` body (PACT §4.1). `configuration` and `metadata` are * accepted and ignored. Order matters where the conformance suite pins it: a `taskId` is * `TASK_NOT_FOUND` even on an otherwise valid message. */ export function parseSendMessage(raw) { let body; try { body = JSON.parse(raw); } catch { throw new A2aError('INVALID_PARAMS', 'Request body is not valid JSON'); } if (!isRecord(body) || !isRecord(body.message)) { throw new A2aError('INVALID_PARAMS', 'Missing message'); } const message = body.message; if (message.taskId !== undefined) { throw new A2aError('TASK_NOT_FOUND', 'Task not found'); } if (typeof message.messageId !== 'string' || message.messageId === '') { throw new A2aError('INVALID_PARAMS', 'messageId is required'); } if (message.contextId !== undefined && typeof message.contextId !== 'string') { throw new A2aError('INVALID_PARAMS', 'contextId must be a string'); } if (message.role !== 'ROLE_USER') { throw new A2aError('INVALID_PARAMS', 'role must be ROLE_USER'); } if (!Array.isArray(message.parts) || message.parts.length === 0) { throw new A2aError('INVALID_PARAMS', 'parts must not be empty'); } const texts = []; for (const part of message.parts) { if (!isRecord(part)) { throw new A2aError('INVALID_PARAMS', 'Invalid part'); } if (typeof part.text === 'string') { texts.push(part.text); } else if ('raw' in part || 'url' in part || 'data' in part) { throw new A2aError('CONTENT_TYPE_NOT_SUPPORTED', 'Only text parts are supported'); } else { throw new A2aError('INVALID_PARAMS', 'Invalid part'); } } const text = texts.join('\n').trim(); if (text === '') { throw new A2aError('INVALID_PARAMS', 'Message text is blank'); } return { messageId: message.messageId, ...(message.contextId !== undefined && message.contextId !== '' ? { contextId: message.contextId } : {}), text, }; } //# sourceMappingURL=protocol.js.map