Skip to content

Commit 9d72082

Browse files
build: enable no-explicit-any in eslint (#190)
# Description The purpose of this PR is removing all the references to keyword 'any' present in the repository, as it is considered a bad practice. Future adoptions will be triggered by the linter automatic checks Fixes #184 🦕
1 parent 3f8cea0 commit 9d72082

File tree

9 files changed

+85
-54
lines changed

9 files changed

+85
-54
lines changed

eslint.config.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ export default defineConfig(
1515
},
1616
{
1717
rules: {
18-
'@typescript-eslint/no-explicit-any': 'off', // Allow the usage of `any` in the project
1918
'@typescript-eslint/no-unused-vars': [
2019
'error',
2120
{
@@ -27,8 +26,9 @@ export default defineConfig(
2726
},
2827
},
2928
{
30-
files: ['test/**/*.spec.ts'],
29+
files: ['test/**/*.ts', 'src/samples/**/*.ts'],
3130
rules: {
31+
'@typescript-eslint/no-explicit-any': 'off', // Allow the usage of `any` in the test files and in samples
3232
'@typescript-eslint/no-unused-expressions': 'off', // Allow unused expressions in test files, for compatibility with 'chai'
3333
},
3434
},

src/client/client.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
TaskArtifactUpdateEvent,
2020
TaskStatusUpdateEvent,
2121
A2ARequest,
22+
JSONRPCErrorResponse,
2223
} from '../types.js'; // Assuming schema.ts is in the same directory or appropriately pathed
2324
import { AGENT_CARD_PATH } from '../constants.js';
2425
import { JsonRpcTransport } from './transports/json_rpc_transport.js';
@@ -283,11 +284,12 @@ export class A2AClient {
283284
params,
284285
this.requestIdCounter++
285286
);
286-
} catch (e: any) {
287+
} catch (e) {
287288
// For compatibility, return JSON-RPC errors as errors instead of throwing transport-agnostic errors
288289
// produced by JsonRpcTransport.
289-
if (isJSONRPCError(e)) {
290-
return e.errorResponse as TExtensionResponse;
290+
const errorResponse = extractJSONRPCError(e);
291+
if (errorResponse) {
292+
return errorResponse as TExtensionResponse;
291293
}
292294
throw e;
293295
}
@@ -442,24 +444,32 @@ export class A2AClient {
442444
jsonrpc: '2.0',
443445
result: result ?? null, // JSON-RPC requires result property on success, it will be null for "void" methods.
444446
} as TResponse;
445-
} catch (e: any) {
447+
} catch (e) {
446448
// For compatibility, return JSON-RPC errors as response objects instead of throwing transport-agnostic errors
447449
// produced by JsonRpcTransport.
448-
if (isJSONRPCError(e)) {
449-
return e.errorResponse as TResponse;
450+
const errorResponse = extractJSONRPCError(e);
451+
if (errorResponse) {
452+
return errorResponse as TResponse;
450453
}
451454
throw e;
452455
}
453456
}
454457
}
455458

456-
function isJSONRPCError(error: any): boolean {
457-
return (
459+
function extractJSONRPCError(error: unknown): JSONRPCErrorResponse {
460+
if (
461+
error instanceof Object &&
458462
'errorResponse' in error &&
459-
error.errorResponse &&
463+
error.errorResponse instanceof Object &&
464+
'jsonrpc' in error.errorResponse &&
460465
error.errorResponse.jsonrpc === '2.0' &&
461-
error.errorResponse.error
462-
);
466+
'error' in error.errorResponse &&
467+
error.errorResponse.error !== null
468+
) {
469+
return error.errorResponse as JSONRPCErrorResponse;
470+
} else {
471+
return undefined;
472+
}
463473
}
464474

465475
// Utility unexported types to properly factor out common "compatibility" logic via invokeJsonRpc.

src/client/transports/json_rpc_transport.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ export class JsonRpcTransport implements A2ATransport {
152152
}
153153

154154
private async _sendRpcRequest<
155+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
155156
TParams extends { [key: string]: any },
156157
TResponse extends JSONRPCResponse,
157158
>(method: string, params: TParams, idOverride: number | undefined): Promise<TResponse> {
@@ -168,11 +169,11 @@ export class JsonRpcTransport implements A2ATransport {
168169

169170
if (!httpResponse.ok) {
170171
let errorBodyText = '(empty or non-JSON response)';
171-
let errorJson: any = {};
172+
let errorJson: JSONRPCErrorResponse;
172173
try {
173174
errorBodyText = await httpResponse.text();
174175
errorJson = JSON.parse(errorBodyText);
175-
} catch (e: any) {
176+
} catch (e) {
176177
throw new Error(
177178
`HTTP error for ${method}! Status: ${httpResponse.status} ${httpResponse.statusText}. Response: ${errorBodyText}`,
178179
{ cause: e }
@@ -218,25 +219,25 @@ export class JsonRpcTransport implements A2ATransport {
218219

219220
private async *_sendStreamingRequest(
220221
method: string,
221-
params: any
222+
params: unknown
222223
): AsyncGenerator<A2AStreamEventData, void, undefined> {
223224
const clientRequestId = this.requestIdCounter++;
224225
const rpcRequest: JSONRPCRequest = {
225226
jsonrpc: '2.0',
226227
method,
227-
params: params as { [key: string]: any },
228+
params: params as { [key: string]: unknown },
228229
id: clientRequestId,
229230
};
230231

231232
const response = await this._fetchRpc(rpcRequest, 'text/event-stream');
232233

233234
if (!response.ok) {
234235
let errorBody = '';
235-
let errorJson: any = {};
236+
let errorJson: JSONRPCErrorResponse;
236237
try {
237238
errorBody = await response.text();
238239
errorJson = JSON.parse(errorBody);
239-
} catch (e: any) {
240+
} catch (e) {
240241
throw new Error(
241242
`HTTP error establishing stream for ${method}: ${response.status} ${response.statusText}. Response: ${errorBody || '(empty)'}`,
242243
{ cause: e }
@@ -305,8 +306,11 @@ export class JsonRpcTransport implements A2ATransport {
305306
}
306307
}
307308
}
308-
} catch (error: any) {
309-
console.error('Error reading or parsing SSE stream:', error.message);
309+
} catch (error) {
310+
console.error(
311+
'Error reading or parsing SSE stream:',
312+
(error instanceof Error && error.message) || 'Error unknown'
313+
);
310314
throw error;
311315
} finally {
312316
reader.releaseLock();
@@ -342,10 +346,11 @@ export class JsonRpcTransport implements A2ATransport {
342346
}
343347

344348
return a2aStreamResponse.result as TStreamItem;
345-
} catch (e: any) {
349+
} catch (e) {
346350
if (
347-
e.message.startsWith('SSE event contained an error') ||
348-
e.message.startsWith("SSE event JSON-RPC response is missing 'result' field")
351+
e instanceof Error &&
352+
(e.message.startsWith('SSE event contained an error') ||
353+
e.message.startsWith("SSE event JSON-RPC response is missing 'result' field"))
349354
) {
350355
throw e;
351356
}
@@ -355,7 +360,7 @@ export class JsonRpcTransport implements A2ATransport {
355360
e
356361
);
357362
throw new Error(
358-
`Failed to parse SSE event data: "${jsonData.substring(0, 100)}...". Original error: ${e.message}`
363+
`Failed to parse SSE event data: "${jsonData.substring(0, 100)}...". Original error: ${(e instanceof Error && e.message) || 'Unknown error'}`
359364
);
360365
}
361366
}

src/server/express/agent_card_handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function agentCardHandler(options: AgentCardHandlerOptions): RequestHandl
2727
try {
2828
const agentCard = await provider();
2929
res.json(agentCard);
30-
} catch (error: any) {
30+
} catch (error) {
3131
console.error('Error fetching agent card:', error);
3232
res.status(500).json({ error: 'Failed to retrieve agent card' });
3333
}

src/server/express/json_rpc_handler.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export function jsonRpcHandler(options: JsonRpcHandlerOptions): RequestHandler {
4343
res.setHeader(HTTP_EXTENSION_HEADER, Array.from(context.activatedExtensions));
4444
}
4545
// Check if it's an AsyncGenerator (stream)
46-
if (typeof (rpcResponseOrStream as any)?.[Symbol.asyncIterator] === 'function') {
46+
if (typeof (rpcResponseOrStream as AsyncGenerator)?.[Symbol.asyncIterator] === 'function') {
4747
const stream = rpcResponseOrStream as AsyncGenerator<
4848
JSONRPCSuccessResponse,
4949
void,
@@ -62,13 +62,17 @@ export function jsonRpcHandler(options: JsonRpcHandlerOptions): RequestHandler {
6262
res.write(`id: ${new Date().getTime()}\n`);
6363
res.write(`data: ${JSON.stringify(event)}\n\n`);
6464
}
65-
} catch (streamError: any) {
65+
} catch (streamError) {
6666
console.error(`Error during SSE streaming (request ${req.body?.id}):`, streamError);
6767
// If the stream itself throws an error, send a final JSONRPCErrorResponse
68-
const a2aError =
69-
streamError instanceof A2AError
70-
? streamError
71-
: A2AError.internalError(streamError.message || 'Streaming error.');
68+
let a2aError: A2AError;
69+
if (streamError instanceof A2AError) {
70+
a2aError = streamError;
71+
} else {
72+
a2aError = A2AError.internalError(
73+
(streamError instanceof Error && streamError.message) || 'Streaming error.'
74+
);
75+
}
7276
const errorResponse: JSONRPCErrorResponse = {
7377
jsonrpc: '2.0',
7478
id: req.body?.id || null, // Use original request ID if available
@@ -93,7 +97,7 @@ export function jsonRpcHandler(options: JsonRpcHandlerOptions): RequestHandler {
9397
const rpcResponse = rpcResponseOrStream as JSONRPCResponse;
9498
res.status(200).json(rpcResponse);
9599
}
96-
} catch (error: any) {
100+
} catch (error) {
97101
// Catch errors from jsonRpcTransportHandler.handle itself (e.g., initial parse error)
98102
console.error('Unhandled error in JSON-RPC POST handler:', error);
99103
const a2aError =
@@ -116,7 +120,7 @@ export function jsonRpcHandler(options: JsonRpcHandlerOptions): RequestHandler {
116120
}
117121

118122
export const jsonErrorHandler: ErrorRequestHandler = (
119-
err: any,
123+
err: unknown,
120124
_req: Request,
121125
res: Response,
122126
next: NextFunction

src/server/request_handler/default_request_handler.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ export class DefaultRequestHandler implements A2ARequestHandler {
159159
eventQueue: ExecutionEventQueue,
160160
options?: {
161161
firstResultResolver?: (value: Message | Task | PromiseLike<Message | Task>) => void;
162-
firstResultRejector?: (reason?: any) => void;
162+
firstResultRejector?: (reason?: unknown) => void;
163163
}
164164
): Promise<void> {
165165
let firstResultSent = false;
@@ -191,7 +191,7 @@ export class DefaultRequestHandler implements A2ARequestHandler {
191191
A2AError.internalError('Execution finished before a message or task was produced.')
192192
);
193193
}
194-
} catch (error: any) {
194+
} catch (error) {
195195
console.error(`Event processing loop failed for task ${taskId}:`, error);
196196
this._handleProcessingError(
197197
error,
@@ -629,11 +629,11 @@ export class DefaultRequestHandler implements A2ARequestHandler {
629629
}
630630

631631
private async _handleProcessingError(
632-
error: any,
632+
error: unknown,
633633
resultManager: ResultManager,
634634
firstResultSent: boolean,
635635
taskId: string,
636-
firstResultRejector?: (reason: any) => void
636+
firstResultRejector?: (reason: unknown) => void
637637
): Promise<void> {
638638
// Non-blocking case with with first result not sent
639639
if (firstResultRejector && !firstResultSent) {
@@ -648,6 +648,7 @@ export class DefaultRequestHandler implements A2ARequestHandler {
648648

649649
// Non-blocking case with first result already sent
650650
const currentTask = resultManager.getCurrentTask();
651+
const errorMessage = (error instanceof Error && error.message) || 'Unknown error';
651652
if (currentTask) {
652653
const statusUpdateFailed: TaskStatusUpdateEvent = {
653654
taskId: currentTask.id,
@@ -658,7 +659,7 @@ export class DefaultRequestHandler implements A2ARequestHandler {
658659
kind: 'message',
659660
role: 'agent',
660661
messageId: uuidv4(),
661-
parts: [{ kind: 'text', text: `Event processing loop failed: ${error.message}` }],
662+
parts: [{ kind: 'text', text: `Event processing loop failed: ${errorMessage}` }],
662663
taskId: currentTask.id,
663664
contextId: currentTask.contextId,
664665
},
@@ -671,10 +672,12 @@ export class DefaultRequestHandler implements A2ARequestHandler {
671672
try {
672673
await resultManager.processEvent(statusUpdateFailed);
673674
} catch (error) {
674-
console.error(`Event processing loop failed for task ${taskId}: ${error.message}`);
675+
console.error(
676+
`Event processing loop failed for task ${taskId}: ${(error instanceof Error && error.message) || 'Unknown error'}`
677+
);
675678
}
676679
} else {
677-
console.error(`Event processing loop failed for task ${taskId}: ${error.message}`);
680+
console.error(`Event processing loop failed for task ${taskId}: ${errorMessage}`);
678681
}
679682
}
680683
}

src/server/transports/jsonrpc_transport_handler.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export class JsonRpcTransportHandler {
2525
* For non-streaming methods, it returns a Promise of a single JSONRPCMessage (Result or ErrorResponse).
2626
*/
2727
public async handle(
28+
// TODO: remove the eslint disable and replace the any (https://github.com/a2aproject/a2a-js/issues/179)
29+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2830
requestBody: any,
2931
context?: ServerCallContext
3032
): Promise<JSONRPCResponse | AsyncGenerator<JSONRPCResponse, void, undefined>> {
@@ -42,11 +44,13 @@ export class JsonRpcTransportHandler {
4244
if (!this.isRequestValid(rpcRequest)) {
4345
throw A2AError.invalidRequest('Invalid JSON-RPC Request.');
4446
}
45-
} catch (error: any) {
47+
} catch (error) {
4648
const a2aError =
4749
error instanceof A2AError
4850
? error
49-
: A2AError.parseError(error.message || 'Failed to parse JSON request.');
51+
: A2AError.parseError(
52+
(error instanceof SyntaxError && error.message) || 'Failed to parse JSON request.'
53+
);
5054
return {
5155
jsonrpc: '2.0',
5256
id: rpcRequest?.id !== undefined ? rpcRequest.id : null,
@@ -94,7 +98,7 @@ export class JsonRpcTransportHandler {
9498
result: event,
9599
};
96100
}
97-
} catch (streamError: any) {
101+
} catch (streamError) {
98102
// If the underlying agent stream throws an error, we need to yield a JSONRPCErrorResponse.
99103
// However, an AsyncGenerator is expected to yield JSONRPCResult.
100104
// This indicates an issue with how errors from the agent's stream are propagated.
@@ -111,7 +115,7 @@ export class JsonRpcTransportHandler {
111115
})();
112116
} else {
113117
// Handle non-streaming methods
114-
let result: any;
118+
let result: unknown;
115119
switch (method) {
116120
case 'message/send':
117121
result = await this.requestHandler.sendMessage(rpcRequest.params, context);
@@ -153,11 +157,15 @@ export class JsonRpcTransportHandler {
153157
result: result,
154158
} as JSONRPCResponse;
155159
}
156-
} catch (error: any) {
157-
const a2aError =
158-
error instanceof A2AError
159-
? error
160-
: A2AError.internalError(error.message || 'An unexpected error occurred.');
160+
} catch (error) {
161+
let a2aError: A2AError;
162+
if (error instanceof A2AError) {
163+
a2aError = error;
164+
} else {
165+
a2aError = A2AError.internalError(
166+
(error instanceof Error && error.message) || 'An unexpected error occurred.'
167+
);
168+
}
161169
return {
162170
jsonrpc: '2.0',
163171
id: requestId,
@@ -189,7 +197,7 @@ export class JsonRpcTransportHandler {
189197
}
190198

191199
// Validates that params is an object with non-empty string keys
192-
private paramsAreValid(params: any): boolean {
200+
private paramsAreValid(params: unknown): boolean {
193201
if (typeof params !== 'object' || params === null || Array.isArray(params)) {
194202
return false;
195203
}

src/server/utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ export function getCurrentTimestamp(): string {
1313
* @param value The value to check.
1414
* @returns True if the value is a plain object, false otherwise.
1515
*/
16-
export function isObject(value: unknown): value is Record<string, any> {
16+
export function isObject(value: unknown): value is Record<string, unknown> {
1717
return typeof value === 'object' && value !== null && !Array.isArray(value);
1818
}
1919

2020
/**
2121
* Type guard to check if an object is a TaskStatus update (lacks 'parts').
2222
* Used to differentiate yielded updates from the handler.
2323
*/
24-
export function isTaskStatusUpdate(update: any): update is Omit<TaskStatus, 'timestamp'> {
24+
export function isTaskStatusUpdate(update: unknown): update is Omit<TaskStatus, 'timestamp'> {
2525
// Check if it has 'state' and NOT 'parts' (which Artifacts have)
2626
return isObject(update) && 'state' in update && !('parts' in update);
2727
}
@@ -30,7 +30,7 @@ export function isTaskStatusUpdate(update: any): update is Omit<TaskStatus, 'tim
3030
* Type guard to check if an object is an Artifact update (has 'parts').
3131
* Used to differentiate yielded updates from the handler.
3232
*/
33-
export function isArtifactUpdate(update: any): update is Artifact {
33+
export function isArtifactUpdate(update: unknown): update is Artifact {
3434
// Check if it has 'parts'
3535
return isObject(update) && 'parts' in update;
3636
}

0 commit comments

Comments
 (0)