mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
chore: remove a2a-adapter and bump @a2a-js/sdk to 0.3.8 (#16800)
This commit is contained in:
Generated
+5
-5
@@ -78,9 +78,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@a2a-js/sdk": {
|
||||
"version": "0.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@a2a-js/sdk/-/sdk-0.3.7.tgz",
|
||||
"integrity": "sha512-1WBghkOjgiKt4rPNje8jlB9VateVQXqyjlc887bY/H8yM82Hlf0+5JW8zB98BPExKAplI5XqtXVH980J6vqi+w==",
|
||||
"version": "0.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@a2a-js/sdk/-/sdk-0.3.8.tgz",
|
||||
"integrity": "sha512-vAg6JQbhOnHTzApsB7nGzCQ9r7PuY4GMr8gt88dIR8Wc8G8RSqVTyTmFeMurgzcYrtHYXS3ru2rnDoGj9UDeSw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"uuid": "^11.1.0"
|
||||
@@ -18395,7 +18395,7 @@
|
||||
"name": "@google/gemini-cli-a2a-server",
|
||||
"version": "0.26.0-nightly.20260115.6cb3ae4e0",
|
||||
"dependencies": {
|
||||
"@a2a-js/sdk": "^0.3.7",
|
||||
"@a2a-js/sdk": "^0.3.8",
|
||||
"@google-cloud/storage": "^7.16.0",
|
||||
"@google/gemini-cli-core": "file:../core",
|
||||
"express": "^5.1.0",
|
||||
@@ -18810,7 +18810,7 @@
|
||||
"version": "0.26.0-nightly.20260115.6cb3ae4e0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@a2a-js/sdk": "^0.3.7",
|
||||
"@a2a-js/sdk": "^0.3.8",
|
||||
"@google-cloud/logging": "^11.2.1",
|
||||
"@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0",
|
||||
"@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@a2a-js/sdk": "^0.3.7",
|
||||
"@a2a-js/sdk": "^0.3.8",
|
||||
"@google-cloud/storage": "^7.16.0",
|
||||
"@google/gemini-cli-core": "file:../core",
|
||||
"express": "^5.1.0",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@a2a-js/sdk": "^0.3.7",
|
||||
"@a2a-js/sdk": "^0.3.8",
|
||||
"@google-cloud/logging": "^11.2.1",
|
||||
"@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0",
|
||||
"@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0",
|
||||
|
||||
@@ -8,7 +8,6 @@ import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import {
|
||||
A2AClientManager,
|
||||
type SendMessageResult,
|
||||
createAdapterFetch,
|
||||
} from './a2a-client-manager.js';
|
||||
import type { AgentCard, Task } from '@a2a-js/sdk';
|
||||
import type { AuthenticationHandler, Client } from '@a2a-js/sdk/client';
|
||||
@@ -317,90 +316,4 @@ describe('A2AClientManager', () => {
|
||||
).rejects.toThrow("Agent 'NonExistentAgent' not found.");
|
||||
});
|
||||
});
|
||||
|
||||
describe('createAdapterFetch', () => {
|
||||
it('normalizes TASK_STATE_ enums to lower-case', async () => {
|
||||
const baseFetch = vi
|
||||
.fn()
|
||||
.mockResolvedValue(
|
||||
new Response(
|
||||
JSON.stringify({ status: { state: 'TASK_STATE_WORKING' } }),
|
||||
),
|
||||
);
|
||||
|
||||
const adapter = createAdapterFetch(baseFetch as typeof fetch);
|
||||
const response = await adapter('http://example.com', {
|
||||
method: 'POST',
|
||||
body: '{}',
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
expect(data.status.state).toBe('working');
|
||||
});
|
||||
|
||||
it('lowercases non-prefixed task states', async () => {
|
||||
const baseFetch = vi
|
||||
.fn()
|
||||
.mockResolvedValue(
|
||||
new Response(JSON.stringify({ status: { state: 'WORKING' } })),
|
||||
);
|
||||
|
||||
const adapter = createAdapterFetch(baseFetch as typeof fetch);
|
||||
const response = await adapter('http://example.com', {
|
||||
method: 'POST',
|
||||
body: '{}',
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
expect(data.status.state).toBe('working');
|
||||
});
|
||||
|
||||
it('bypasses adapter for JSON-RPC requests', async () => {
|
||||
const baseFetch = vi.fn().mockResolvedValue(new Response('{}'));
|
||||
const adapter = createAdapterFetch(baseFetch as typeof fetch);
|
||||
const rpcBody = JSON.stringify({ jsonrpc: '2.0', method: 'foo' });
|
||||
|
||||
await adapter('http://example.com', {
|
||||
method: 'POST',
|
||||
body: rpcBody,
|
||||
});
|
||||
|
||||
// Verify baseFetch was called with original body, not modified
|
||||
expect(baseFetch).toHaveBeenCalledWith(
|
||||
'http://example.com',
|
||||
expect.objectContaining({ body: rpcBody }),
|
||||
);
|
||||
});
|
||||
|
||||
it('applies dialect translation for remote REST requests', async () => {
|
||||
const baseFetch = vi.fn().mockResolvedValue(new Response('{}'));
|
||||
const adapter = createAdapterFetch(baseFetch as typeof fetch);
|
||||
const originalBody = JSON.stringify({
|
||||
message: {
|
||||
role: 'user',
|
||||
parts: [{ kind: 'text', text: 'hi' }],
|
||||
},
|
||||
});
|
||||
|
||||
await adapter('https://remote-agent.com/v1/message:send', {
|
||||
method: 'POST',
|
||||
body: originalBody,
|
||||
});
|
||||
|
||||
// Verify body WAS modified:
|
||||
// 1. role: 'user' -> 'ROLE_USER'
|
||||
// 2. parts mapped to content, kind stripped
|
||||
const expectedBody = JSON.stringify({
|
||||
message: {
|
||||
role: 'ROLE_USER',
|
||||
content: [{ text: 'hi' }],
|
||||
},
|
||||
});
|
||||
|
||||
expect(baseFetch).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({ body: expectedBody }),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -73,10 +73,6 @@ export class A2AClientManager {
|
||||
fetchImpl = createAuthenticatingFetchWithRetry(fetch, authHandler);
|
||||
}
|
||||
|
||||
// Wrap with custom adapter for ADK Reasoning Engine compatibility
|
||||
// TODO: Remove this when a2a-js fixes compatibility
|
||||
fetchImpl = createAdapterFetch(fetchImpl);
|
||||
|
||||
const resolver = new DefaultAgentCardResolver({ fetchImpl });
|
||||
|
||||
const options = ClientFactoryOptions.createFrom(
|
||||
@@ -220,134 +216,3 @@ export class A2AClientManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps TaskState proto-JSON enums to lower-case strings.
|
||||
*/
|
||||
function mapTaskState(state: string | undefined): string | undefined {
|
||||
if (!state) return state;
|
||||
if (state.startsWith('TASK_STATE_')) {
|
||||
return state.replace('TASK_STATE_', '').toLowerCase();
|
||||
}
|
||||
return state.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a fetch implementation that adapts standard A2A SDK requests to the
|
||||
* proto-JSON dialect and endpoint shapes required by Vertex AI Agent Engine.
|
||||
*/
|
||||
export function createAdapterFetch(baseFetch: typeof fetch): typeof fetch {
|
||||
return async (
|
||||
input: RequestInfo | URL,
|
||||
init?: RequestInit,
|
||||
): Promise<Response> => {
|
||||
const body = init?.body;
|
||||
// Protocol Detection
|
||||
// JSON-RPC requests bypass the adapter as they are standard-compliant and
|
||||
// don't require the dialect translation intended for Vertex AI REST bindings.
|
||||
// This logic can be removed when a2a-js/sdk is fully compliant.
|
||||
let effectiveBody = body;
|
||||
if (typeof body === 'string') {
|
||||
try {
|
||||
const jsonBody = JSON.parse(body);
|
||||
|
||||
// If the SDK decided to use JSON-RPC, we bypass the adapter because
|
||||
// JSON-RPC requests are correctly supported in a2a-js/sdk.
|
||||
if (jsonBody.jsonrpc === '2.0') {
|
||||
return await baseFetch(input, init);
|
||||
}
|
||||
|
||||
// Dialect Mapping (REST / HTTP+JSON)
|
||||
// Apply translation for Vertex AI Agent Engine compatibility.
|
||||
const message = jsonBody.message || jsonBody;
|
||||
if (message && typeof message === 'object') {
|
||||
// Role: user -> ROLE_USER, agent/model -> ROLE_AGENT
|
||||
if (message.role === 'user') message.role = 'ROLE_USER';
|
||||
if (message.role === 'agent' || message.role === 'model') {
|
||||
message.role = 'ROLE_AGENT';
|
||||
}
|
||||
|
||||
// Strip SDK-specific 'kind' field
|
||||
delete message.kind;
|
||||
|
||||
// Map 'parts' to 'content' (Proto-JSON dialect often uses 'content' or typed parts)
|
||||
// Also strip 'kind' from parts.
|
||||
if (Array.isArray(message.parts)) {
|
||||
message.content = message.parts.map(
|
||||
(p: { kind?: string; text?: string }) => {
|
||||
const { kind: _k, ...rest } = p;
|
||||
// If it's a simple text part, ensure it matches { text: "..." }
|
||||
if (p.kind === 'text') return { text: p.text };
|
||||
return rest;
|
||||
},
|
||||
);
|
||||
delete message.parts;
|
||||
}
|
||||
}
|
||||
|
||||
effectiveBody = JSON.stringify(jsonBody);
|
||||
} catch (error) {
|
||||
debugLogger.debug(
|
||||
'[A2AClientManager] Failed to parse request body for dialect translation:',
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const response = await baseFetch(input, { ...init, body: effectiveBody });
|
||||
|
||||
if (response.ok) {
|
||||
try {
|
||||
const responseData = await response.clone().json();
|
||||
const result =
|
||||
responseData.task || responseData.message || responseData;
|
||||
|
||||
// Restore 'kind' for the SDK and a2aUtils parsing
|
||||
if (result && typeof result === 'object' && !result.kind) {
|
||||
if (responseData.task || (result.id && result.status)) {
|
||||
result.kind = 'task';
|
||||
} else if (responseData.message || result.messageId) {
|
||||
result.kind = 'message';
|
||||
}
|
||||
}
|
||||
|
||||
// Restore 'kind' on parts so extractMessageText works
|
||||
if (result?.parts && Array.isArray(result.parts)) {
|
||||
for (const part of result.parts) {
|
||||
if (!part.kind) {
|
||||
if (part.file) part.kind = 'file';
|
||||
else if (part.data) part.kind = 'data';
|
||||
else if (part.text) part.kind = 'text';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively restore 'kind' on artifact parts
|
||||
if (result?.artifacts && Array.isArray(result.artifacts)) {
|
||||
for (const artifact of result.artifacts) {
|
||||
if (artifact.parts && Array.isArray(artifact.parts)) {
|
||||
for (const part of artifact.parts) {
|
||||
if (!part.kind) {
|
||||
if (part.file) part.kind = 'file';
|
||||
else if (part.data) part.kind = 'data';
|
||||
else if (part.text) part.kind = 'text';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Map Task States back to SDK expectations
|
||||
if (result && typeof result === 'object' && result.status) {
|
||||
result.status.state = mapTaskState(result.status.state);
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify(result), response);
|
||||
} catch (_e) {
|
||||
// Non-JSON response or unwrapping failure
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user