Skip to content

Commit f447e4e

Browse files
fix(grpc): preserve null values in protobuf Struct (#578)
## Summary - update the ts-proto generator from v2.6.1 to v2.11.0 so generated `google.protobuf.Struct` wrappers preserve JavaScript `null` - encode JavaScript `null` as `google.protobuf.NullValue.NULL_VALUE` - decode the protobuf `nullValue` case back to JavaScript `null` - add a wire-level regression test covering direct, nested, and array metadata values ## Root cause The generated `Value.wrap()` implementation assigned the literal JavaScript `null` to a protobuf enum field. `@bufbuild/protobuf` requires an integer enum value, so gRPC serialization failed with `invalid int32: object`. The reverse conversion also returned the enum value `0` instead of restoring JSON `null`. The newer ts-proto output corrects both directions so `google.protobuf.Struct` fields preserve JSON semantics across the gRPC wire. ## Impact Tasks, messages, and other A2A gRPC payloads can now contain valid JSON `null` values in metadata or other `Struct`-backed fields without crashing or changing them to `0` after deserialization. ## Target branch This PR is rebased onto and directly targets `epic/1.0_breaking_changes` for the stable 1.0.0 SDK work. ## Generation note The checked-in 1.0 bindings contain post-generation adjustments. A full `buf generate` currently rewrites those bindings and adds unrelated generated files, so this PR refreshes only the affected `Struct` artifact together with the generator pin to keep the fix scoped. ## Validation - `npm test` — 66 files, 1,375 tests passed - `npm run test:edge` — 49 files, 1,081 tests passed, 2 skipped - `npm run build` — passed - `npm run test-build` — passed - `npm run lint:ci` — passed in GitHub Actions Fixes #576
1 parent 7cd30de commit f447e4e

3 files changed

Lines changed: 41 additions & 12 deletions

File tree

src/grpc/buf.gen.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ inputs:
1111
- google/rpc/status.proto
1212
- google/rpc/error_details.proto
1313
plugins:
14-
- remote: buf.build/community/stephenh-ts-proto:v2.6.1
14+
- remote: buf.build/community/stephenh-ts-proto:v2.11.0
1515
opt:
1616
- oneof=unions-value
1717
- importSuffix=.js
@@ -21,4 +21,4 @@ plugins:
2121
- outputJsonMethods=false
2222
- outputPartialMethods=false
2323
- useDate=string
24-
out: ./pb
24+
out: ./pb

src/grpc/pb/google/protobuf/struct.ts

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { A2AServiceService, SendMessageResponse } from '../../../src/grpc/pb/a2a.js';
3+
4+
describe('gRPC protobuf serialization', () => {
5+
it('preserves null values in task metadata', () => {
6+
const response: SendMessageResponse = {
7+
payload: {
8+
$case: 'task',
9+
value: {
10+
id: 'task-1',
11+
contextId: 'context-1',
12+
status: undefined,
13+
artifacts: [],
14+
history: [],
15+
metadata: {
16+
direct: null,
17+
nested: { value: null },
18+
items: [null, 'present'],
19+
},
20+
},
21+
},
22+
};
23+
24+
const serialized = A2AServiceService.sendMessage.responseSerialize(response);
25+
const deserialized = A2AServiceService.sendMessage.responseDeserialize(serialized);
26+
27+
expect(deserialized).toEqual(response);
28+
});
29+
});

0 commit comments

Comments
 (0)