From e46bb1f665bdddf7261a5eea8bde90cc415cf96c Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Fri, 31 Jul 2026 13:25:11 -0700 Subject: [PATCH 01/15] feat(generator): add enable_telemetry_tracing option and client interface field --- .../gapic-generator-typescript/typescript/src/generator.ts | 7 +++++++ .../typescript/src/schema/api.ts | 2 ++ .../typescript/src/schema/naming.ts | 1 + 3 files changed, 10 insertions(+) diff --git a/core/generator/gapic-generator-typescript/typescript/src/generator.ts b/core/generator/gapic-generator-typescript/typescript/src/generator.ts index 84a8e818781f..c6cbf042839e 100644 --- a/core/generator/gapic-generator-typescript/typescript/src/generator.ts +++ b/core/generator/gapic-generator-typescript/typescript/src/generator.ts @@ -86,6 +86,7 @@ export class Generator { restNumericEnums?: boolean; mixinsOverride?: string[]; format?: string | string[]; + enableTelemetryTracing?: boolean; private root: protobuf.Root; @@ -227,6 +228,10 @@ export class Generator { } } + private readEnableTelemetryTracing() { + this.enableTelemetryTracing = this.paramMap['enable_telemetry_tracing'] === 'true'; + } + private readLegacyProtoLoad() { if (this.paramMap['legacy-proto-load'] === 'true') { this.legacyProtoLoad = true; @@ -274,6 +279,7 @@ export class Generator { this.readLegacyProtoLoad(); this.readRestNumericEnums(); this.readFormat(); + this.readEnableTelemetryTracing(); } } @@ -334,6 +340,7 @@ export class Generator { legacyProtoLoad: this.legacyProtoLoad, restNumericEnums: this.restNumericEnums, mixinsOverridden: this.mixinsOverride !== undefined, + enableTelemetryTracing: this.enableTelemetryTracing, }); return api; } diff --git a/core/generator/gapic-generator-typescript/typescript/src/schema/api.ts b/core/generator/gapic-generator-typescript/typescript/src/schema/api.ts index a49572f920f2..2e8c02aadc69 100644 --- a/core/generator/gapic-generator-typescript/typescript/src/schema/api.ts +++ b/core/generator/gapic-generator-typescript/typescript/src/schema/api.ts @@ -45,6 +45,7 @@ export class API { restNumericEnums: boolean; documentationUri: any; newIssueUri: string; + enableTelemetryTracing?: boolean; title?: string; static isIgnoredService( @@ -112,6 +113,7 @@ export class API { this.documentationUri = options.serviceYaml?.publishing?.documentation_uri ?? ''; this.newIssueUri = options.serviceYaml?.publishing?.new_issue_uri ?? ''; + this.enableTelemetryTracing = options.enableTelemetryTracing ?? false; this.title = options.serviceYaml?.title; const [allResourceDatabase, resourceDatabase] = diff --git a/core/generator/gapic-generator-typescript/typescript/src/schema/naming.ts b/core/generator/gapic-generator-typescript/typescript/src/schema/naming.ts index 0d95f68a732c..49c90c9f1b63 100644 --- a/core/generator/gapic-generator-typescript/typescript/src/schema/naming.ts +++ b/core/generator/gapic-generator-typescript/typescript/src/schema/naming.ts @@ -30,6 +30,7 @@ export interface Options { legacyProtoLoad?: boolean; restNumericEnums?: boolean; mixinsOverridden?: boolean; + enableTelemetryTracing?: boolean; } export class Naming { From 2808f115278074a01456a532bf07f00ea87cbd6c Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Fri, 31 Jul 2026 13:27:07 -0700 Subject: [PATCH 02/15] test(generator): add unit tests for enableTelemetryTracing option --- .../typescript/test/unit/api.ts | 21 +++++++++++++++++++ .../typescript/test/unit/naming.ts | 13 ++++++++++++ 2 files changed, 34 insertions(+) diff --git a/core/generator/gapic-generator-typescript/typescript/test/unit/api.ts b/core/generator/gapic-generator-typescript/typescript/test/unit/api.ts index 4be052b5bccc..0fddfb7f3e71 100644 --- a/core/generator/gapic-generator-typescript/typescript/test/unit/api.ts +++ b/core/generator/gapic-generator-typescript/typescript/test/unit/api.ts @@ -325,4 +325,25 @@ describe('src/schema/api.ts', () => { assert('.google.cloud.example.v1.MessageB' in secondCallMessages); }); }); + + it('should set enableTelemetryTracing option', () => { + const fd = {} as protos.google.protobuf.FileDescriptorProto; + fd.name = 'google/cloud/test/v1/test.proto'; + fd.package = 'google.cloud.test.v1'; + fd.service = [{} as protos.google.protobuf.ServiceDescriptorProto]; + fd.service[0].name = 'ZService'; + fd.service[0].options = { + '.google.api.defaultHost': 'hostname.example.com:443', + }; + const apiWithTracing = new API([fd], 'google.cloud.test.v1', { + grpcServiceConfig: {} as protos.grpc.service_config.ServiceConfig, + enableTelemetryTracing: true, + }); + assert.strictEqual(apiWithTracing.enableTelemetryTracing, true); + + const apiWithoutTracing = new API([fd], 'google.cloud.test.v1', { + grpcServiceConfig: {} as protos.grpc.service_config.ServiceConfig, + }); + assert.strictEqual(apiWithoutTracing.enableTelemetryTracing, false); + }); }); diff --git a/core/generator/gapic-generator-typescript/typescript/test/unit/naming.ts b/core/generator/gapic-generator-typescript/typescript/test/unit/naming.ts index 2aec137f42ec..8ab6e94db8ff 100644 --- a/core/generator/gapic-generator-typescript/typescript/test/unit/naming.ts +++ b/core/generator/gapic-generator-typescript/typescript/test/unit/naming.ts @@ -218,4 +218,17 @@ describe('src/schema/naming.ts', () => { assert(naming); }); }); + + it('supports enableTelemetryTracing option', () => { + const descriptor = {} as protos.google.protobuf.FileDescriptorProto; + descriptor.package = 'google.namespace.service.v1beta1'; + descriptor.service = [{} as protos.google.protobuf.ServiceDescriptorProto]; + const options: Options = { + grpcServiceConfig: {} as protos.grpc.service_config.ServiceConfig, + enableTelemetryTracing: true, + }; + const naming = new Naming([descriptor], options); + assert(naming); + assert.strictEqual(naming.name, 'Service'); + }); }); From 6e3e0aa7ee375d003dc218940b8e2138d06bb4d5 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Mon, 3 Aug 2026 20:14:50 -0700 Subject: [PATCH 03/15] fix: update readEnableTelemetryTracing to read enable-telemetry-tracing option --- .../gapic-generator-typescript/typescript/src/generator.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/generator/gapic-generator-typescript/typescript/src/generator.ts b/core/generator/gapic-generator-typescript/typescript/src/generator.ts index c6cbf042839e..b66849bed378 100644 --- a/core/generator/gapic-generator-typescript/typescript/src/generator.ts +++ b/core/generator/gapic-generator-typescript/typescript/src/generator.ts @@ -229,7 +229,9 @@ export class Generator { } private readEnableTelemetryTracing() { - this.enableTelemetryTracing = this.paramMap['enable_telemetry_tracing'] === 'true'; + if (this.paramMap['enable-telemetry-tracing'] === 'true') { + this.enableTelemetryTracing = true; + } } private readLegacyProtoLoad() { From cb18df146e072f1f5c674489413e89b512d1d817 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Mon, 3 Aug 2026 20:57:12 -0700 Subject: [PATCH 04/15] test: add unit tests for Generator class in gapic-generator-typescript --- .../typescript/test/unit/generator.ts | 399 ++++++++++++++++++ 1 file changed, 399 insertions(+) create mode 100644 core/generator/gapic-generator-typescript/typescript/test/unit/generator.ts diff --git a/core/generator/gapic-generator-typescript/typescript/test/unit/generator.ts b/core/generator/gapic-generator-typescript/typescript/test/unit/generator.ts new file mode 100644 index 000000000000..f74665455511 --- /dev/null +++ b/core/generator/gapic-generator-typescript/typescript/test/unit/generator.ts @@ -0,0 +1,399 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import assert from 'assert'; +import {describe, it, beforeEach, afterEach} from 'mocha'; +import {Generator} from '../../src/generator.js'; +import type {API} from '../../src/schema/api.js'; +import type * as protos from '../../../protos/index.js'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; + +interface TestGenerator { + getParamMap(parameter: string): void; + readGrpcServiceConfig(): Promise; + readBundleConfig(): void; + readServiceYaml(): void; + readPublishPackageName(): void; + readMainServiceName(): void; + readTemplates(): void; + readRest(): void; + readDiregapic(): void; + readHandwrittenLayer(): void; + readFormat(): void; + readEnableTelemetryTracing(): void; + readLegacyProtoLoad(): void; + readRestNumericEnums(): void; + readMixins(): void; + addProtosToResponse(): void; + buildAPIObject(): API; +} + +describe('src/generator.ts', () => { + let generator: Generator; + + beforeEach(() => { + generator = new Generator(); + }); + + function getPrivate(gen: Generator): TestGenerator { + return gen as unknown as TestGenerator; + } + + it('should initialize with default values', () => { + assert.deepStrictEqual(generator.request, {}); + assert.deepStrictEqual(generator.response, {}); + assert.deepStrictEqual(generator.grpcServiceConfig, {}); + assert.deepStrictEqual(generator.paramMap, {}); + assert.deepStrictEqual(generator.templates, [ + 'typescript_gapic', + 'typescript_packing_test', + 'typescript_samples', + ]); + }); + + describe('parameter parsing', () => { + it('should parse parameter options string', () => { + getPrivate(generator).getParamMap( + 'package-name=@google-cloud/test,transport=rest,diregapic=true', + ); + assert.strictEqual( + generator.paramMap['package-name'], + '@google-cloud/test', + ); + assert.strictEqual(generator.paramMap['transport'], 'rest'); + assert.strictEqual(generator.paramMap['diregapic'], 'true'); + }); + + it('should handle parameter without explicit value', () => { + getPrivate(generator).getParamMap('handwritten-layer'); + assert.strictEqual(generator.paramMap['handwritten-layer'], 'true'); + }); + + it('should handle quoted parameters', () => { + getPrivate(generator).getParamMap('"main-service=TestService"'); + assert.strictEqual(generator.paramMap['main-service'], 'TestService'); + }); + + it('should convert parameter keys to kebab-case', () => { + getPrivate(generator).getParamMap( + 'main_service=TestService,legacy_proto_load=true', + ); + assert.strictEqual(generator.paramMap['main-service'], 'TestService'); + assert.strictEqual(generator.paramMap['legacy-proto-load'], 'true'); + }); + }); + + describe('option readers', () => { + it('should read publish package name', () => { + generator.paramMap['package-name'] = '@google-cloud/speech'; + getPrivate(generator).readPublishPackageName(); + assert.strictEqual(generator.publishName, '@google-cloud/speech'); + }); + + it('should read main service name', () => { + generator.paramMap['main-service'] = 'Speech'; + getPrivate(generator).readMainServiceName(); + assert.strictEqual(generator.mainServiceName, 'Speech'); + }); + + it('should read custom templates and metadata flag', () => { + generator.paramMap['template'] = 'custom_template_1;custom_template_2'; + generator.paramMap['metadata'] = 'true'; + getPrivate(generator).readTemplates(); + assert.deepStrictEqual(generator.templates, [ + 'custom_template_1', + 'custom_template_2', + 'typescript_gapic_metadata', + ]); + }); + + it('should read transport rest option', () => { + generator.paramMap['transport'] = 'rest'; + getPrivate(generator).readRest(); + assert.strictEqual(generator.rest, true); + }); + + it('should read diregapic option', () => { + generator.paramMap['diregapic'] = 'true'; + getPrivate(generator).readDiregapic(); + assert.strictEqual(generator.diregapic, true); + assert.strictEqual(generator.rest, true); + }); + + it('should read handwritten-layer option', () => { + generator.paramMap['handwritten-layer'] = 'true'; + getPrivate(generator).readHandwrittenLayer(); + assert.strictEqual(generator.handwrittenLayer, true); + }); + + it('should read format option', () => { + generator.paramMap['format'] = 'esm;cjs'; + getPrivate(generator).readFormat(); + assert.deepStrictEqual(generator.format, ['esm', 'cjs']); + }); + + it('should read telemetry tracing option', () => { + generator.paramMap['enable-telemetry-tracing'] = 'true'; + getPrivate(generator).readEnableTelemetryTracing(); + assert.strictEqual(generator.enableTelemetryTracing, true); + }); + + it('should read legacy proto load option', () => { + generator.paramMap['legacy-proto-load'] = 'true'; + getPrivate(generator).readLegacyProtoLoad(); + assert.strictEqual(generator.legacyProtoLoad, true); + }); + + it('should read rest numeric enums option', () => { + generator.paramMap['rest-numeric-enums'] = 'true'; + getPrivate(generator).readRestNumericEnums(); + assert.strictEqual(generator.restNumericEnums, true); + }); + + it('should read mixins option', () => { + generator.paramMap['mixins'] = + 'google.iam.v1.IAMPolicy;google.longrunning.Operations'; + getPrivate(generator).readMixins(); + assert.deepStrictEqual(generator.mixinsOverride, [ + 'google.iam.v1.IAMPolicy', + 'google.longrunning.Operations', + ]); + }); + }); + + describe('file configurations', () => { + const tmpDir = path.join(os.tmpdir(), 'generator-test-' + Date.now()); + + beforeEach(() => { + fs.mkdirSync(tmpDir, {recursive: true}); + }); + + afterEach(() => { + fs.rmSync(tmpDir, {recursive: true, force: true}); + }); + + it('should throw when gRPC service config file does not exist', async () => { + generator.paramMap['grpc-service-config'] = path.join( + tmpDir, + 'nonexistent.json', + ); + await assert.rejects(async () => { + await getPrivate(generator).readGrpcServiceConfig(); + }, /ERROR: File .* cannot be opened\./); + }); + + it('should read valid gRPC service config file', async () => { + const grpcConfigPath = path.join(tmpDir, 'grpc_service_config.json'); + fs.writeFileSync( + grpcConfigPath, + JSON.stringify({ + methodConfig: [ + { + name: [{service: 'google.test.v1.TestService'}], + timeout: '60s', + }, + ], + }), + ); + generator.paramMap['grpc-service-config'] = grpcConfigPath; + await getPrivate(generator).readGrpcServiceConfig(); + assert.ok(generator.grpcServiceConfig.methodConfig); + assert.strictEqual(generator.grpcServiceConfig.methodConfig.length, 1); + }); + + it('should throw when bundle config file does not exist', () => { + generator.paramMap['bundle-config'] = path.join( + tmpDir, + 'nonexistent.yaml', + ); + assert.throws(() => { + getPrivate(generator).readBundleConfig(); + }, /ERROR: File .* cannot be opened\./); + }); + + it('should throw when service yaml file does not exist', () => { + generator.paramMap['service-yaml'] = path.join( + tmpDir, + 'nonexistent.yaml', + ); + assert.throws(() => { + getPrivate(generator).readServiceYaml(); + }, /ERROR: File .* cannot be opened\./); + }); + + it('should read valid service yaml file', () => { + const serviceYamlPath = path.join(tmpDir, 'service.yaml'); + fs.writeFileSync( + serviceYamlPath, + `title: Test Service +apis: + - name: google.iam.v1.IAMPolicy + - name: google.longrunning.Operations +`, + ); + generator.paramMap['service-yaml'] = serviceYamlPath; + getPrivate(generator).readServiceYaml(); + assert.strictEqual(generator.serviceYaml?.title, 'Test Service'); + assert.deepStrictEqual(generator.serviceYaml?.apis, [ + 'google.iam.v1.IAMPolicy', + 'google.longrunning.Operations', + ]); + }); + + it('should override mixins when mixinsOverride is set', () => { + generator.mixinsOverride = ['google.iam.v1.IAMPolicy']; + getPrivate(generator).readServiceYaml(); + assert.deepStrictEqual(generator.serviceYaml?.apis, [ + 'google.iam.v1.IAMPolicy', + ]); + + generator.mixinsOverride = ['none']; + getPrivate(generator).readServiceYaml(); + assert.deepStrictEqual(generator.serviceYaml?.apis, []); + }); + + it('should read valid bundle config file', () => { + const bundleConfigPath = path.join(tmpDir, 'bundle.yaml'); + fs.writeFileSync( + bundleConfigPath, + `interfaces: + - name: google.test.v1.TestService + methods: + - name: ListItems + batching: + thresholds: + element_count_threshold: 10 + request_byte_threshold: 100 + delay_threshold_millis: 1000 + batch_descriptor: + batched_field: items + discriminator_fields: + - parent + subresponse_field: items +`, + ); + generator.paramMap['bundle-config'] = bundleConfigPath; + getPrivate(generator).readBundleConfig(); + assert.strictEqual(generator.bundleConfigs.length, 1); + assert.strictEqual(generator.bundleConfigs[0].serviceName, 'TestService'); + assert.strictEqual(generator.bundleConfigs[0].methodName, 'ListItems'); + }); + }); + + describe('addProtosToResponse', () => { + it('should populate response with proto.list file', () => { + generator.request = { + protoFile: [ + {name: 'google/cloud/test/v1/test.proto'}, + {name: 'google/cloud/test/v1/other.proto'}, + ], + } as protos.google.protobuf.compiler.CodeGeneratorRequest; + generator.response = { + file: [], + } as protos.google.protobuf.compiler.CodeGeneratorResponse; + + getPrivate(generator).addProtosToResponse(); + + assert.strictEqual(generator.response.file?.length, 1); + assert.strictEqual(generator.response.file?.[0].name, 'proto.list'); + assert.strictEqual( + generator.response.file?.[0].content, + 'google/cloud/test/v1/test.proto\ngoogle/cloud/test/v1/other.proto\n', + ); + }); + }); + + describe('buildAPIObject', () => { + it('should throw error if protos define no services', () => { + generator.request = { + protoFile: [ + { + name: 'google/cloud/test/v1/test.proto', + package: 'google.cloud.test.v1', + }, + ], + fileToGenerate: ['google/cloud/test/v1/test.proto'], + } as protos.google.protobuf.compiler.CodeGeneratorRequest; + + assert.throws(() => { + getPrivate(generator).buildAPIObject(); + }, /ERROR: Protos do not define any service/); + }); + + it('should create API object when services are defined', () => { + generator.request = { + protoFile: [ + { + name: 'google/cloud/test/v1/test.proto', + package: 'google.cloud.test.v1', + service: [ + { + name: 'TestService', + options: { + '.google.api.defaultHost': 'test.googleapis.com', + }, + }, + ], + }, + ], + fileToGenerate: ['google/cloud/test/v1/test.proto'], + } as protos.google.protobuf.compiler.CodeGeneratorRequest; + + const api = getPrivate(generator).buildAPIObject(); + assert.ok(api); + assert.strictEqual(api.packageName, 'google.cloud.test.v1'); + }); + }); + + describe('processTemplates', () => { + it('should throw error if template location does not exist', async () => { + generator.templates = ['nonexistent_template_dir']; + const api = {} as API; + + await assert.rejects(async () => { + await generator.processTemplates(api); + }, /Template directory .* does not exist\./); + }); + }); + + describe('generate', () => { + it('should capture error message in response on failure', async () => { + const originalStdoutWrite = process.stdout.write; + let writtenBuffer: Buffer | null = null; + + process.stdout.write = (chunk: Uint8Array | string) => { + writtenBuffer = Buffer.from(chunk); + return true; + }; + + try { + generator.request = { + protoFile: [], + } as protos.google.protobuf.compiler.CodeGeneratorRequest; + + await generator.generate(); + + assert.strictEqual( + generator.response.error, + 'ERROR: Protos do not define any service, client library cannot be generated.', + ); + assert.ok(writtenBuffer); + } finally { + process.stdout.write = originalStdoutWrite; + } + }); + }); +}); From 2945a1576d183b299fdf91a4b17c3c1021662e2e Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Mon, 3 Aug 2026 21:00:31 -0700 Subject: [PATCH 05/15] refactor: rename helper to getTestGenerator in generator unit tests --- .../typescript/test/unit/generator.ts | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/core/generator/gapic-generator-typescript/typescript/test/unit/generator.ts b/core/generator/gapic-generator-typescript/typescript/test/unit/generator.ts index f74665455511..73b82e1e931f 100644 --- a/core/generator/gapic-generator-typescript/typescript/test/unit/generator.ts +++ b/core/generator/gapic-generator-typescript/typescript/test/unit/generator.ts @@ -48,7 +48,7 @@ describe('src/generator.ts', () => { generator = new Generator(); }); - function getPrivate(gen: Generator): TestGenerator { + function getTestGenerator(gen: Generator): TestGenerator { return gen as unknown as TestGenerator; } @@ -66,7 +66,7 @@ describe('src/generator.ts', () => { describe('parameter parsing', () => { it('should parse parameter options string', () => { - getPrivate(generator).getParamMap( + getTestGenerator(generator).getParamMap( 'package-name=@google-cloud/test,transport=rest,diregapic=true', ); assert.strictEqual( @@ -78,17 +78,17 @@ describe('src/generator.ts', () => { }); it('should handle parameter without explicit value', () => { - getPrivate(generator).getParamMap('handwritten-layer'); + getTestGenerator(generator).getParamMap('handwritten-layer'); assert.strictEqual(generator.paramMap['handwritten-layer'], 'true'); }); it('should handle quoted parameters', () => { - getPrivate(generator).getParamMap('"main-service=TestService"'); + getTestGenerator(generator).getParamMap('"main-service=TestService"'); assert.strictEqual(generator.paramMap['main-service'], 'TestService'); }); it('should convert parameter keys to kebab-case', () => { - getPrivate(generator).getParamMap( + getTestGenerator(generator).getParamMap( 'main_service=TestService,legacy_proto_load=true', ); assert.strictEqual(generator.paramMap['main-service'], 'TestService'); @@ -99,20 +99,20 @@ describe('src/generator.ts', () => { describe('option readers', () => { it('should read publish package name', () => { generator.paramMap['package-name'] = '@google-cloud/speech'; - getPrivate(generator).readPublishPackageName(); + getTestGenerator(generator).readPublishPackageName(); assert.strictEqual(generator.publishName, '@google-cloud/speech'); }); it('should read main service name', () => { generator.paramMap['main-service'] = 'Speech'; - getPrivate(generator).readMainServiceName(); + getTestGenerator(generator).readMainServiceName(); assert.strictEqual(generator.mainServiceName, 'Speech'); }); it('should read custom templates and metadata flag', () => { generator.paramMap['template'] = 'custom_template_1;custom_template_2'; generator.paramMap['metadata'] = 'true'; - getPrivate(generator).readTemplates(); + getTestGenerator(generator).readTemplates(); assert.deepStrictEqual(generator.templates, [ 'custom_template_1', 'custom_template_2', @@ -122,51 +122,51 @@ describe('src/generator.ts', () => { it('should read transport rest option', () => { generator.paramMap['transport'] = 'rest'; - getPrivate(generator).readRest(); + getTestGenerator(generator).readRest(); assert.strictEqual(generator.rest, true); }); it('should read diregapic option', () => { generator.paramMap['diregapic'] = 'true'; - getPrivate(generator).readDiregapic(); + getTestGenerator(generator).readDiregapic(); assert.strictEqual(generator.diregapic, true); assert.strictEqual(generator.rest, true); }); it('should read handwritten-layer option', () => { generator.paramMap['handwritten-layer'] = 'true'; - getPrivate(generator).readHandwrittenLayer(); + getTestGenerator(generator).readHandwrittenLayer(); assert.strictEqual(generator.handwrittenLayer, true); }); it('should read format option', () => { generator.paramMap['format'] = 'esm;cjs'; - getPrivate(generator).readFormat(); + getTestGenerator(generator).readFormat(); assert.deepStrictEqual(generator.format, ['esm', 'cjs']); }); it('should read telemetry tracing option', () => { generator.paramMap['enable-telemetry-tracing'] = 'true'; - getPrivate(generator).readEnableTelemetryTracing(); + getTestGenerator(generator).readEnableTelemetryTracing(); assert.strictEqual(generator.enableTelemetryTracing, true); }); it('should read legacy proto load option', () => { generator.paramMap['legacy-proto-load'] = 'true'; - getPrivate(generator).readLegacyProtoLoad(); + getTestGenerator(generator).readLegacyProtoLoad(); assert.strictEqual(generator.legacyProtoLoad, true); }); it('should read rest numeric enums option', () => { generator.paramMap['rest-numeric-enums'] = 'true'; - getPrivate(generator).readRestNumericEnums(); + getTestGenerator(generator).readRestNumericEnums(); assert.strictEqual(generator.restNumericEnums, true); }); it('should read mixins option', () => { generator.paramMap['mixins'] = 'google.iam.v1.IAMPolicy;google.longrunning.Operations'; - getPrivate(generator).readMixins(); + getTestGenerator(generator).readMixins(); assert.deepStrictEqual(generator.mixinsOverride, [ 'google.iam.v1.IAMPolicy', 'google.longrunning.Operations', @@ -191,7 +191,7 @@ describe('src/generator.ts', () => { 'nonexistent.json', ); await assert.rejects(async () => { - await getPrivate(generator).readGrpcServiceConfig(); + await getTestGenerator(generator).readGrpcServiceConfig(); }, /ERROR: File .* cannot be opened\./); }); @@ -209,7 +209,7 @@ describe('src/generator.ts', () => { }), ); generator.paramMap['grpc-service-config'] = grpcConfigPath; - await getPrivate(generator).readGrpcServiceConfig(); + await getTestGenerator(generator).readGrpcServiceConfig(); assert.ok(generator.grpcServiceConfig.methodConfig); assert.strictEqual(generator.grpcServiceConfig.methodConfig.length, 1); }); @@ -220,7 +220,7 @@ describe('src/generator.ts', () => { 'nonexistent.yaml', ); assert.throws(() => { - getPrivate(generator).readBundleConfig(); + getTestGenerator(generator).readBundleConfig(); }, /ERROR: File .* cannot be opened\./); }); @@ -230,7 +230,7 @@ describe('src/generator.ts', () => { 'nonexistent.yaml', ); assert.throws(() => { - getPrivate(generator).readServiceYaml(); + getTestGenerator(generator).readServiceYaml(); }, /ERROR: File .* cannot be opened\./); }); @@ -245,7 +245,7 @@ apis: `, ); generator.paramMap['service-yaml'] = serviceYamlPath; - getPrivate(generator).readServiceYaml(); + getTestGenerator(generator).readServiceYaml(); assert.strictEqual(generator.serviceYaml?.title, 'Test Service'); assert.deepStrictEqual(generator.serviceYaml?.apis, [ 'google.iam.v1.IAMPolicy', @@ -255,13 +255,13 @@ apis: it('should override mixins when mixinsOverride is set', () => { generator.mixinsOverride = ['google.iam.v1.IAMPolicy']; - getPrivate(generator).readServiceYaml(); + getTestGenerator(generator).readServiceYaml(); assert.deepStrictEqual(generator.serviceYaml?.apis, [ 'google.iam.v1.IAMPolicy', ]); generator.mixinsOverride = ['none']; - getPrivate(generator).readServiceYaml(); + getTestGenerator(generator).readServiceYaml(); assert.deepStrictEqual(generator.serviceYaml?.apis, []); }); @@ -286,7 +286,7 @@ apis: `, ); generator.paramMap['bundle-config'] = bundleConfigPath; - getPrivate(generator).readBundleConfig(); + getTestGenerator(generator).readBundleConfig(); assert.strictEqual(generator.bundleConfigs.length, 1); assert.strictEqual(generator.bundleConfigs[0].serviceName, 'TestService'); assert.strictEqual(generator.bundleConfigs[0].methodName, 'ListItems'); @@ -305,7 +305,7 @@ apis: file: [], } as protos.google.protobuf.compiler.CodeGeneratorResponse; - getPrivate(generator).addProtosToResponse(); + getTestGenerator(generator).addProtosToResponse(); assert.strictEqual(generator.response.file?.length, 1); assert.strictEqual(generator.response.file?.[0].name, 'proto.list'); @@ -329,7 +329,7 @@ apis: } as protos.google.protobuf.compiler.CodeGeneratorRequest; assert.throws(() => { - getPrivate(generator).buildAPIObject(); + getTestGenerator(generator).buildAPIObject(); }, /ERROR: Protos do not define any service/); }); @@ -352,7 +352,7 @@ apis: fileToGenerate: ['google/cloud/test/v1/test.proto'], } as protos.google.protobuf.compiler.CodeGeneratorRequest; - const api = getPrivate(generator).buildAPIObject(); + const api = getTestGenerator(generator).buildAPIObject(); assert.ok(api); assert.strictEqual(api.packageName, 'google.cloud.test.v1'); }); From 048fff3cff962c31e22ea71a8a18b3f0f2a75fd5 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Mon, 3 Aug 2026 21:10:56 -0700 Subject: [PATCH 06/15] refactor: update type assertions in generator unit tests --- .../typescript/test/unit/generator.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/generator/gapic-generator-typescript/typescript/test/unit/generator.ts b/core/generator/gapic-generator-typescript/typescript/test/unit/generator.ts index 73b82e1e931f..5ec3b3e1b8a1 100644 --- a/core/generator/gapic-generator-typescript/typescript/test/unit/generator.ts +++ b/core/generator/gapic-generator-typescript/typescript/test/unit/generator.ts @@ -300,10 +300,10 @@ apis: {name: 'google/cloud/test/v1/test.proto'}, {name: 'google/cloud/test/v1/other.proto'}, ], - } as protos.google.protobuf.compiler.CodeGeneratorRequest; + } as unknown as protos.google.protobuf.compiler.CodeGeneratorRequest; generator.response = { file: [], - } as protos.google.protobuf.compiler.CodeGeneratorResponse; + } as unknown as protos.google.protobuf.compiler.CodeGeneratorResponse; getTestGenerator(generator).addProtosToResponse(); @@ -382,7 +382,7 @@ apis: try { generator.request = { protoFile: [], - } as protos.google.protobuf.compiler.CodeGeneratorRequest; + } as unknown as protos.google.protobuf.compiler.CodeGeneratorRequest; await generator.generate(); From d539b80647c387468a8376440c11c9ad7da9bb5a Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Fri, 31 Jul 2026 13:25:11 -0700 Subject: [PATCH 07/15] feat(generator): add enable_telemetry_tracing option and client interface field --- core/packages/gax/src/clientInterface.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/packages/gax/src/clientInterface.ts b/core/packages/gax/src/clientInterface.ts index b9f3bd8b8e9c..165626715867 100644 --- a/core/packages/gax/src/clientInterface.ts +++ b/core/packages/gax/src/clientInterface.ts @@ -42,6 +42,8 @@ export interface ClientOptions // No preference; exception will be thrown if both are set to different values. universeDomain?: string; universe_domain?: string; + enableTelemetryTracing?: boolean; + internalTelemetryInfo?: InternalTelemetry; } export interface Descriptors { @@ -93,3 +95,10 @@ export interface PaginationResponse< nextPageRequest?: RequestObject; rawResponse?: ResponseObject; } + +export interface InternalTelemetry { + gcpClientService?: string; + gcpVersion?: string; + gcpRepo?: string; + gcpArtifact?: string; +} \ No newline at end of file From 3a3cfcd24320f10abce3d5104de62f6fd4532b92 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Mon, 3 Aug 2026 18:23:24 -0700 Subject: [PATCH 08/15] feat(gax): accept clientOptions in createApiCall for telemetry tracing --- core/packages/gax/src/createApiCall.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/packages/gax/src/createApiCall.ts b/core/packages/gax/src/createApiCall.ts index b3fdee987a6d..7b3bdb712a91 100644 --- a/core/packages/gax/src/createApiCall.ts +++ b/core/packages/gax/src/createApiCall.ts @@ -27,6 +27,7 @@ import { RequestType, SimpleCallbackFunction, } from './apitypes'; +import { ClientOptions } from './clientInterface'; import {Descriptor} from './descriptor'; import {CallOptions, CallSettings, convertRetryOptions} from './gax'; import {retryable} from './normalCalls/retries'; @@ -59,6 +60,7 @@ export function createApiCall( descriptor?: Descriptor, // eslint-disable-next-line @typescript-eslint/no-unused-vars _fallback?: boolean | 'proto' | 'rest', // unused here, used in fallback.ts implementation + clientOptions?: ClientOptions ): GaxCall { // we want to be able to accept both promise resolving to a function and a // function. Currently client librares are only calling this method with a @@ -67,6 +69,7 @@ export function createApiCall( // the following apiCaller will be used for all calls of this function... const apiCaller = createAPICaller(settings, descriptor); + const tracingEnabled = clientOptions?.enableTelemetryTracing && process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED; return ( request: RequestType, callOptions?: CallOptions, From 09814b125044875e4953802b8f9dec8ff8fe61ee Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Mon, 3 Aug 2026 18:31:44 -0700 Subject: [PATCH 09/15] feat(gax): check internalTelemetryInfo for tracingEnabled in createApiCall --- core/packages/gax/src/createApiCall.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/packages/gax/src/createApiCall.ts b/core/packages/gax/src/createApiCall.ts index 7b3bdb712a91..f74ba17fb81f 100644 --- a/core/packages/gax/src/createApiCall.ts +++ b/core/packages/gax/src/createApiCall.ts @@ -69,8 +69,9 @@ export function createApiCall( // the following apiCaller will be used for all calls of this function... const apiCaller = createAPICaller(settings, descriptor); - const tracingEnabled = clientOptions?.enableTelemetryTracing && process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED; - return ( + // Check if telemetry tracing is enabled and also check if internal telemetry information has been passed through the templates + const tracingEnabled = clientOptions?.enableTelemetryTracing && process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED && clientOptions?.internalTelemetryInfo !== undefined; + const invokeCall = ( request: RequestType, callOptions?: CallOptions, callback?: APICallback, @@ -167,4 +168,9 @@ export function createApiCall( // or to cancel the ongoing call. return currentApiCaller.result(ongoingCall); }; + if (tracingEnabled) { + return invokeCall; + } else { + return invokeCall; + } } From 7f972f54a540f6e18db74297942d9125f4609e57 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Mon, 3 Aug 2026 16:43:07 -0700 Subject: [PATCH 10/15] feat(generator): import getGaxTracer when enableTelemetryTracing is set --- .../cjs/typescript_gapic/src/$version/$service_client.ts.njk | 1 + .../esm/typescript_gapic/esm/src/$version/$service_client.ts.njk | 1 + 2 files changed, 2 insertions(+) diff --git a/core/generator/gapic-generator-typescript/templates/cjs/typescript_gapic/src/$version/$service_client.ts.njk b/core/generator/gapic-generator-typescript/templates/cjs/typescript_gapic/src/$version/$service_client.ts.njk index 90553e89770f..24f467204106 100644 --- a/core/generator/gapic-generator-typescript/templates/cjs/typescript_gapic/src/$version/$service_client.ts.njk +++ b/core/generator/gapic-generator-typescript/templates/cjs/typescript_gapic/src/$version/$service_client.ts.njk @@ -55,6 +55,7 @@ import {loggingUtils as logging} from 'google-gax'; import jsonProtos = require('../../protos/protos.json'); import {loggingUtils as logging, decodeAnyProtosInArray} from 'google-gax'; {%- endif %} +{% if api.enableTelemetryTracing %} import { getGaxTracer } from 'google-gax'; {% endif %} /** * Client JSON configuration object, loaded from diff --git a/core/generator/gapic-generator-typescript/templates/esm/typescript_gapic/esm/src/$version/$service_client.ts.njk b/core/generator/gapic-generator-typescript/templates/esm/typescript_gapic/esm/src/$version/$service_client.ts.njk index e505db0eef11..71bd062a9ff5 100644 --- a/core/generator/gapic-generator-typescript/templates/esm/typescript_gapic/esm/src/$version/$service_client.ts.njk +++ b/core/generator/gapic-generator-typescript/templates/esm/typescript_gapic/esm/src/$version/$service_client.ts.njk @@ -50,6 +50,7 @@ import {fileURLToPath} from 'url'; import {getJSON} from '../json-helper.cjs'; // @ts-ignore const dirname = path.dirname(fileURLToPath(import.meta.url)); +{% if api.enableTelemetryTracing %} import { getGaxTracer } from 'google-gax'; {% endif %} /** * Client JSON configuration object, loaded from From f4aee4dfb1f2d4998d2b963ddab8b89e6e045278 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Mon, 3 Aug 2026 18:03:19 -0700 Subject: [PATCH 11/15] feat(generator): add internalTelemetryInfo configuration when enableTelemetryTracing is enabled --- .../src/$version/$service_client.ts.njk | 10 +++++++++- .../esm/src/$version/$service_client.ts.njk | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/core/generator/gapic-generator-typescript/templates/cjs/typescript_gapic/src/$version/$service_client.ts.njk b/core/generator/gapic-generator-typescript/templates/cjs/typescript_gapic/src/$version/$service_client.ts.njk index 24f467204106..6b15efaa0cb9 100644 --- a/core/generator/gapic-generator-typescript/templates/cjs/typescript_gapic/src/$version/$service_client.ts.njk +++ b/core/generator/gapic-generator-typescript/templates/cjs/typescript_gapic/src/$version/$service_client.ts.njk @@ -55,7 +55,6 @@ import {loggingUtils as logging} from 'google-gax'; import jsonProtos = require('../../protos/protos.json'); import {loggingUtils as logging, decodeAnyProtosInArray} from 'google-gax'; {%- endif %} -{% if api.enableTelemetryTracing %} import { getGaxTracer } from 'google-gax'; {% endif %} /** * Client JSON configuration object, loaded from @@ -198,6 +197,15 @@ export class {{ service.name }}Client { } {%- endif %} + {%- if api.enableTelemetryTracing %} + opts.internalTelemetryInfo = { + gcpClientService: '{{ api.loggingName }}', + gcpClientVersion: '{{ api.naming.version }}', + gcpRepo: 'googleapis/google-cloud-node', + gcpArtifact: '{{ api.publishName }}', + } + {%- endif %} + // Choose either gRPC or proto-over-HTTP implementation of google-gax. this._gaxModule = {% if not api.legacyProtoLoad %}opts.fallback ? gaxInstance.fallback : gaxInstance{% else %}gax{% endif %}; diff --git a/core/generator/gapic-generator-typescript/templates/esm/typescript_gapic/esm/src/$version/$service_client.ts.njk b/core/generator/gapic-generator-typescript/templates/esm/typescript_gapic/esm/src/$version/$service_client.ts.njk index 71bd062a9ff5..886198d4e43d 100644 --- a/core/generator/gapic-generator-typescript/templates/esm/typescript_gapic/esm/src/$version/$service_client.ts.njk +++ b/core/generator/gapic-generator-typescript/templates/esm/typescript_gapic/esm/src/$version/$service_client.ts.njk @@ -50,7 +50,6 @@ import {fileURLToPath} from 'url'; import {getJSON} from '../json-helper.cjs'; // @ts-ignore const dirname = path.dirname(fileURLToPath(import.meta.url)); -{% if api.enableTelemetryTracing %} import { getGaxTracer } from 'google-gax'; {% endif %} /** * Client JSON configuration object, loaded from @@ -204,6 +203,15 @@ export class {{ service.name }}Client { gaxInstance = gax as typeof gax; } {%- endif %} + + {%- if api.enableTelemetryTracing %} + opts.internalTelemetryInfo = { + gcpClientService: '{{ api.loggingName }}', + gcpClientVersion: '{{ api.naming.version }}', + gcpRepo: 'googleapis/google-cloud-node', + gcpArtifact: '{{ api.publishName }}', + } + {%- endif %} // Choose either gRPC or proto-over-HTTP implementation of google-gax. this._gaxModule = {% if not api.legacyProtoLoad %}opts.fallback ? gaxInstance.fallback : gaxInstance{% else %}gax{% endif %}; From 81d21dbbf5cf68138e8b7334b6488a43efff9c40 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Tue, 21 Jul 2026 12:06:47 -0700 Subject: [PATCH 12/15] refactor(gax): extract invokeCall and return conditionally based on tracingEnabled --- core/packages/gax/src/createApiCall.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/packages/gax/src/createApiCall.ts b/core/packages/gax/src/createApiCall.ts index f74ba17fb81f..232bf3916f07 100644 --- a/core/packages/gax/src/createApiCall.ts +++ b/core/packages/gax/src/createApiCall.ts @@ -70,7 +70,10 @@ export function createApiCall( const apiCaller = createAPICaller(settings, descriptor); // Check if telemetry tracing is enabled and also check if internal telemetry information has been passed through the templates - const tracingEnabled = clientOptions?.enableTelemetryTracing && process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED && clientOptions?.internalTelemetryInfo !== undefined; + const tracingEnabled = + clientOptions?.enableTelemetryTracing && + process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED && + clientOptions?.internalTelemetryInfo !== undefined; const invokeCall = ( request: RequestType, callOptions?: CallOptions, @@ -168,6 +171,7 @@ export function createApiCall( // or to cancel the ongoing call. return currentApiCaller.result(ongoingCall); }; + if (tracingEnabled) { return invokeCall; } else { From 54595e18e10cda0ca992e098ed10e747807b2b7f Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Wed, 22 Jul 2026 13:38:28 -0700 Subject: [PATCH 13/15] fix(gax): gate OpenTelemetry tracing behind clientOptions and env var --- core/packages/gax/src/createApiCall.ts | 3 +++ core/packages/gax/test/unit/apiCallable.ts | 24 ++++++++++++++++++++++ core/packages/gax/test/unit/utils.ts | 4 ++++ 3 files changed, 31 insertions(+) diff --git a/core/packages/gax/src/createApiCall.ts b/core/packages/gax/src/createApiCall.ts index 232bf3916f07..5c71300866a0 100644 --- a/core/packages/gax/src/createApiCall.ts +++ b/core/packages/gax/src/createApiCall.ts @@ -18,6 +18,7 @@ * Provides function wrappers that implement page streaming and retrying. */ +import { ClientOptions } from './clientInterface'; import {createAPICaller} from './apiCaller'; import { APICallback, @@ -173,8 +174,10 @@ export function createApiCall( }; if (tracingEnabled) { + console.log('tracing enabled'); return invokeCall; } else { + console.log('tracing disabled'); return invokeCall; } } diff --git a/core/packages/gax/test/unit/apiCallable.ts b/core/packages/gax/test/unit/apiCallable.ts index f341ec12f01f..71a32712445f 100644 --- a/core/packages/gax/test/unit/apiCallable.ts +++ b/core/packages/gax/test/unit/apiCallable.ts @@ -329,6 +329,30 @@ describe('createApiCall', () => { ); } }); + + describe('in regards to OpenTelemetry Tracing', () => { + let consoleSpy: sinon.SinonSpy; + beforeEach(() => { + consoleSpy = sinon.spy(console, 'log'); + }); + + afterEach(() => { + consoleSpy.restore(); + delete process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED; + }); + + it('logs "tracing enabled" when GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED and ClientOptions field is set', () => { + process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED = 'true'; + const mockClientOptions = { enableTelemetryTracing: true }; + createApiCall(() => { }, { clientOptions: mockClientOptions }); + assert(consoleSpy.calledWith('tracing enabled')); + }); + + it('logs "tracing disabled" when GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED is not set', () => { + createApiCall(() => { }); + assert(consoleSpy.calledWith('tracing disabled')); + }); + }); }); describe('Promise', () => { diff --git a/core/packages/gax/test/unit/utils.ts b/core/packages/gax/test/unit/utils.ts index 81440541a1fb..9b1f80177332 100644 --- a/core/packages/gax/test/unit/utils.ts +++ b/core/packages/gax/test/unit/utils.ts @@ -16,6 +16,7 @@ import {GaxCallPromise} from '../../src/apitypes'; import {createApiCall as realCreateApiCall} from '../../src/createApiCall'; +import {ClientOptions} from '../../src/clientInterface'; import * as gax from '../../src/gax'; import {GoogleError} from '../../src/googleError'; import {Descriptor} from '../../src/descriptor'; @@ -42,6 +43,7 @@ export interface Options { returnCancelFunc?: boolean; cancel?: Function; deadline?: string; + clientOptions?: ClientOptions; } export function createApiCall(func: Function, opts?: Options) { @@ -78,6 +80,8 @@ export function createApiCall(func: Function, opts?: Options) { }), settings, descriptor, + undefined, + opts?.clientOptions, ) as GaxCallPromise; } From 194b57bb8ca08441a8a2accc4259420867ba1fee Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Mon, 3 Aug 2026 18:47:52 -0700 Subject: [PATCH 14/15] feat(gax): add TracerHelper for observability trace contexts --- .../gax/src/observability/TracerHelper.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 core/packages/gax/src/observability/TracerHelper.ts diff --git a/core/packages/gax/src/observability/TracerHelper.ts b/core/packages/gax/src/observability/TracerHelper.ts new file mode 100644 index 000000000000..8dce457e2b6e --- /dev/null +++ b/core/packages/gax/src/observability/TracerHelper.ts @@ -0,0 +1,21 @@ +import { context, trace, Tracer } from '@opentelemetry/api'; +import * as grpc from '@grpc/grpc-js'; + +export interface StaticTraceContext { + gcpClientService?: string; + gcpVersion?: string; + gcpRepo?: string; + gcpArtifact?: string; +} + +export interface DynamicTraceContext { + clientName: string; + methodName: string; + rpcType: 'grpc' | 'http'; +} + +export function getGaxTracer(): Tracer { + return trace.getTracer('google-gax'); +} + +export async function traceAttempt() { } \ No newline at end of file From dc03a0f32783fb305894b5fcb9beacabb86dc274 Mon Sep 17 00:00:00 2001 From: Shivanee Persaud Date: Mon, 3 Aug 2026 18:48:24 -0700 Subject: [PATCH 15/15] update TracerHelper.ts with user changes --- core/packages/gax/src/observability/TracerHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/packages/gax/src/observability/TracerHelper.ts b/core/packages/gax/src/observability/TracerHelper.ts index 8dce457e2b6e..9e6173317623 100644 --- a/core/packages/gax/src/observability/TracerHelper.ts +++ b/core/packages/gax/src/observability/TracerHelper.ts @@ -1,4 +1,4 @@ -import { context, trace, Tracer } from '@opentelemetry/api'; +import { context, trace, Tracer } from `@opentelemetry/api`; import * as grpc from '@grpc/grpc-js'; export interface StaticTraceContext {