From ec8bf97e69b8b6e1e1c9d121955380aefdc7cd8e Mon Sep 17 00:00:00 2001 From: Alisa Novikova <62909685+alisa-alisa@users.noreply.github.com> Date: Wed, 11 Mar 2026 07:16:43 -0700 Subject: [PATCH] Remove zod --- packages/core/src/agents/a2aUtils.test.ts | 6 +- packages/core/src/agents/a2aUtils.ts | 164 +++++++++------------- 2 files changed, 68 insertions(+), 102 deletions(-) diff --git a/packages/core/src/agents/a2aUtils.test.ts b/packages/core/src/agents/a2aUtils.test.ts index 2fc20abae7..d5ab74f037 100644 --- a/packages/core/src/agents/a2aUtils.test.ts +++ b/packages/core/src/agents/a2aUtils.test.ts @@ -286,9 +286,9 @@ describe('a2aUtils', () => { expect(normalized.name).toBe('my-agent'); // @ts-expect-error - testing dynamic preservation expect(normalized.customField).toBe('keep-me'); - expect(normalized.description).toBe(''); - expect(normalized.skills).toEqual([]); - expect(normalized.defaultInputModes).toEqual([]); + expect(normalized.description).toBeUndefined(); + expect(normalized.skills).toBeUndefined(); + expect(normalized.defaultInputModes).toBeUndefined(); }); it('should normalize and synchronize interfaces while preserving other fields', () => { diff --git a/packages/core/src/agents/a2aUtils.ts b/packages/core/src/agents/a2aUtils.ts index 89766f0843..227908d07d 100644 --- a/packages/core/src/agents/a2aUtils.ts +++ b/packages/core/src/agents/a2aUtils.ts @@ -5,7 +5,6 @@ */ import * as grpc from '@grpc/grpc-js'; -import { z } from 'zod'; import type { Message, Part, @@ -21,32 +20,6 @@ import type { SendMessageResult } from './a2a-client-manager.js'; export const AUTH_REQUIRED_MSG = `[Authorization Required] The agent has indicated it requires authorization to proceed. Please follow the agent's instructions.`; -const AgentInterfaceSchema = z - .object({ - url: z.string().default(''), - transport: z.string().optional(), - protocolBinding: z.string().optional(), - }) - .passthrough(); - -const AgentCardSchema = z - .object({ - name: z.string().default('unknown'), - description: z.string().default(''), - url: z.string().default(''), - version: z.string().default(''), - protocolVersion: z.string().default(''), - capabilities: z.record(z.unknown()).default({}), - skills: z.array(z.union([z.string(), z.record(z.unknown())])).default([]), - defaultInputModes: z.array(z.string()).default([]), - defaultOutputModes: z.array(z.string()).default([]), - - additionalInterfaces: z.array(AgentInterfaceSchema).optional(), - supportedInterfaces: z.array(AgentInterfaceSchema).optional(), - preferredTransport: z.string().optional(), - }) - .passthrough(); - /** * Reassembles incremental A2A streaming updates into a coherent result. * Shows sequential status/messages followed by all reassembled artifacts. @@ -247,37 +220,82 @@ export function normalizeAgentCard(card: unknown): AgentCard { throw new Error('Agent card is missing.'); } - // Use Zod to validate and parse the card, ensuring safe defaults and narrowing types. - const parsed = AgentCardSchema.parse(card); - // Narrowing to AgentCard interface after runtime validation. + // Narrowing to AgentCard interface. // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - const result = parsed as unknown as AgentCard; + const result = card as unknown as AgentCard; - // Normalize interfaces and synchronize both interface fields. - const normalizedInterfaces = extractNormalizedInterfaces(parsed); - result.additionalInterfaces = normalizedInterfaces; + // 1. Normalize and Sync Interfaces + const interfaces = normalizeInterfaces(card); + result.additionalInterfaces = interfaces; // Sync supportedInterfaces for backward compatibility. // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion const legacyResult = result as unknown as Record; - legacyResult['supportedInterfaces'] = normalizedInterfaces; + legacyResult['supportedInterfaces'] = interfaces; - // Fallback preferredTransport: If not specified, default to GRPC if available. + // 2. Fallback preferredTransport: If not specified, default to GRPC if available. if ( !result.preferredTransport && - normalizedInterfaces.some((i) => i.transport === 'GRPC') + interfaces.some((i) => i.transport === 'GRPC') ) { result.preferredTransport = 'GRPC'; } - // Fallback: If top-level URL is missing, use the first interface's URL. - if (result.url === '' && normalizedInterfaces.length > 0) { - result.url = normalizedInterfaces[0].url; + // 3. Fallback: If top-level URL is missing, use the first interface's URL. + if ((!result.url || result.url === '') && interfaces.length > 0) { + result.url = interfaces[0].url; } return result; } +/** + * Extracts and normalizes interfaces from the card, handling protocol version fallbacks. + */ +function normalizeInterfaces(card: Record): AgentInterface[] { + const additional = card['additionalInterfaces']; + const supported = card['supportedInterfaces']; + + let rawInterfaces: unknown[] = []; + if (Array.isArray(additional)) { + rawInterfaces = additional; + } else if (Array.isArray(supported)) { + rawInterfaces = supported; + } + + return rawInterfaces.filter(isObject).map((i) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + const intf = i as unknown as AgentInterface; + normalizeInterface(intf); + return intf; + }); +} + +/** + * Normalizes a single AgentInterface. + */ +function normalizeInterface(intf: AgentInterface): void { + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + const raw = intf as unknown as Record; + + // Normalize 'transport' from 'protocolBinding' if missing. + const protocolBinding = raw['protocolBinding']; + if (!intf.transport && isString(protocolBinding)) { + intf.transport = protocolBinding; + } + + // Robust URL: Ensure the URL has a scheme (except for gRPC). + if ( + intf.url && + !intf.url.includes('://') && + !intf.url.startsWith('/') && + intf.transport !== 'GRPC' + ) { + // Default to http:// for insecure REST/JSON-RPC if scheme is missing. + intf.url = `http://${intf.url}`; + } +} + /** * Returns gRPC channel credentials based on the URL scheme. */ @@ -332,65 +350,6 @@ export function extractIdsFromResponse(result: SendMessageResult): { return { contextId, taskId, clearTaskId }; } -/** - * Extracts and normalizes interfaces from the card, handling protocol version fallbacks. - * Preserves all original fields to maintain SDK compatibility. - */ -function extractNormalizedInterfaces( - card: Record, -): AgentInterface[] { - const rawInterfaces = - getArray(card, 'additionalInterfaces') || - getArray(card, 'supportedInterfaces'); - - if (!rawInterfaces) { - return []; - } - - const mapped: AgentInterface[] = []; - for (const i of rawInterfaces) { - if (isObject(i)) { - // Use schema to validate interface object. - const parsed = AgentInterfaceSchema.parse(i); - // Narrowing to AgentInterface after runtime validation. - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - const normalized = parsed as unknown as AgentInterface & { - protocolBinding?: string; - }; - - // Normalize 'transport' from 'protocolBinding' if missing. - if (!normalized.transport && normalized.protocolBinding) { - normalized.transport = normalized.protocolBinding; - } - - // Robust URL: Ensure the URL has a scheme (except for gRPC). - if ( - normalized.url && - !normalized.url.includes('://') && - !normalized.url.startsWith('/') && - normalized.transport !== 'GRPC' - ) { - // Default to http:// for insecure REST/JSON-RPC if scheme is missing. - normalized.url = `http://${normalized.url}`; - } - - mapped.push(normalized as AgentInterface); - } - } - return mapped; -} - -/** - * Safely extracts an array property from an object. - */ -function getArray( - obj: Record, - key: string, -): unknown[] | undefined { - const val = obj[key]; - return Array.isArray(val) ? val : undefined; -} - // Type Guards function isTextPart(part: Part): part is TextPart { @@ -417,6 +376,13 @@ export function isTerminalState(state: TaskState | undefined): boolean { ); } +/** + * Type guard to check if a value is a string. + */ +function isString(val: unknown): val is string { + return typeof val === 'string'; +} + /** * Type guard to check if a value is a non-array object. */