diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/package.json b/aws-distro-opentelemetry-node-autoinstrumentation/package.json index 6cbd8cac..195a8e70 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/package.json +++ b/aws-distro-opentelemetry-node-autoinstrumentation/package.json @@ -38,6 +38,10 @@ "url": "https://github.com/aws-observability/aws-otel-js-instrumentation/issues" }, "devDependencies": { + "@aws-sdk/client-bedrock": "3.632.0", + "@aws-sdk/client-bedrock-agent": "3.632.0", + "@aws-sdk/client-bedrock-agent-runtime": "3.632.0", + "@aws-sdk/client-bedrock-runtime": "3.632.0", "@aws-sdk/client-kinesis": "3.632.0", "@aws-sdk/client-s3": "3.632.0", "@aws-sdk/client-sqs": "3.632.0", diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-attribute-keys.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-attribute-keys.ts index 53ee92a7..e2378272 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-attribute-keys.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-attribute-keys.ts @@ -31,4 +31,8 @@ export const AWS_ATTRIBUTE_KEYS: { [key: string]: string } = { AWS_SQS_QUEUE_NAME: 'aws.sqs.queue.name', AWS_KINESIS_STREAM_NAME: 'aws.kinesis.stream.name', AWS_DYNAMODB_TABLE_NAMES: SEMATTRS_AWS_DYNAMODB_TABLE_NAMES, + AWS_BEDROCK_DATA_SOURCE_ID: 'aws.bedrock.data_source.id', + AWS_BEDROCK_KNOWLEDGE_BASE_ID: 'aws.bedrock.knowledge_base.id', + AWS_BEDROCK_AGENT_ID: 'aws.bedrock.agent.id', + AWS_BEDROCK_GUARDRAIL_ID: 'aws.bedrock.guardrail.id', }; diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-metric-attribute-generator.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-metric-attribute-generator.ts index daf90d7a..e58b600d 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-metric-attribute-generator.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-metric-attribute-generator.ts @@ -55,6 +55,8 @@ const NORMALIZED_DYNAMO_DB_SERVICE_NAME: string = 'AWS::DynamoDB'; const NORMALIZED_KINESIS_SERVICE_NAME: string = 'AWS::Kinesis'; const NORMALIZED_S3_SERVICE_NAME: string = 'AWS::S3'; const NORMALIZED_SQS_SERVICE_NAME: string = 'AWS::SQS'; +const NORMALIZED_BEDROCK_SERVICE_NAME: string = 'AWS::Bedrock'; +const NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME: string = 'AWS::BedrockRuntime'; const DB_CONNECTION_RESOURCE_TYPE: string = 'DB::Connection'; // As per https://opentelemetry.io/docs/specs/semconv/resource/#service, if service name is not specified, SDK defaults @@ -317,10 +319,19 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator { * Cloud Control resource format as much as possible, with special attention to services we * can detect remote resource information for. Long term, we would like to normalize service name * in the upstream. + * + * For Bedrock, Bedrock Agent, and Bedrock Agent Runtime, we can align with AWS Cloud Control and use + * AWS::Bedrock for RemoteService. For BedrockRuntime, we are using AWS::BedrockRuntime + * as the associated remote resource (Model) is not listed in Cloud Control. */ private static normalizeRemoteServiceName(span: ReadableSpan, serviceName: string): string { if (AwsSpanProcessingUtil.isAwsSDKSpan(span)) { - return 'AWS::' + serviceName; + const awsSdkServiceMapping: { [key: string]: string } = { + BedrockAgent: NORMALIZED_BEDROCK_SERVICE_NAME, + BedrockAgentRuntime: NORMALIZED_BEDROCK_SERVICE_NAME, + BedrockRuntime: NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME, + }; + return awsSdkServiceMapping[serviceName] || 'AWS::' + serviceName; } return serviceName; } @@ -369,6 +380,31 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator { remoteResourceIdentifier = SqsUrlParser.getQueueName( AwsMetricAttributeGenerator.escapeDelimiters(span.attributes[AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_URL]) ); + } else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID)) { + remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::Agent'; + remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters( + span.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID] + ); + } else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID)) { + remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::DataSource'; + remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters( + span.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID] + ); + } else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID)) { + remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::Guardrail'; + remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters( + span.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID] + ); + } else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID)) { + remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::KnowledgeBase'; + remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters( + span.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID] + ); + } else if (AwsSpanProcessingUtil.isKeyPresent(span, AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL)) { + remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::Model'; + remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters( + span.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL] + ); } } else if (AwsSpanProcessingUtil.isDBSpan(span)) { remoteResourceType = DB_CONNECTION_RESOURCE_TYPE; diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-span-processing-util.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-span-processing-util.ts index 72974e79..d67a5d5c 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-span-processing-util.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-span-processing-util.ts @@ -38,6 +38,10 @@ export class AwsSpanProcessingUtil { static MAX_KEYWORD_LENGTH: number = 27; static SQL_DIALECT_PATTERN: string = '^(?:' + AwsSpanProcessingUtil.getDialectKeywords().join('|') + ')\\b'; + // TODO: Use Semantic Conventions once upgraded + static GEN_AI_REQUEST_MODEL: string = 'gen_ai.request.model'; + static GEN_AI_SYSTEM: string = 'gen_ai.system'; + static getDialectKeywords(): string[] { return SQL_DIALECT_KEYWORDS_JSON.keywords; } diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/bedrock.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/bedrock.ts new file mode 100644 index 00000000..ffeafacd --- /dev/null +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/bedrock.ts @@ -0,0 +1,221 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Attributes, DiagLogger, Span, SpanKind, Tracer } from '@opentelemetry/api'; +import { + AwsSdkInstrumentationConfig, + NormalizedRequest, + NormalizedResponse, +} from '@opentelemetry/instrumentation-aws-sdk'; +import { AWS_ATTRIBUTE_KEYS } from '../../../aws-attribute-keys'; +import { RequestMetadata, ServiceExtension } from '../../../third-party/otel/aws/services/ServiceExtension'; +import { AwsSpanProcessingUtil } from '../../../aws-span-processing-util'; + +const AGENT_ID: string = 'agentId'; +const KNOWLEDGE_BASE_ID: string = 'knowledgeBaseId'; +const DATA_SOURCE_ID: string = 'dataSourceId'; +const GUARDRAIL_ID: string = 'guardrailId'; +const MODEL_ID: string = 'modelId'; +const AWS_BEDROCK_SYSTEM: string = 'aws_bedrock'; + +const AGENT_OPERATIONS = [ + 'CreateAgentActionGroup', + 'CreateAgentAlias', + 'DeleteAgentActionGroup', + 'DeleteAgentAlias', + 'DeleteAgent', + 'DeleteAgentVersion', + 'GetAgentActionGroup', + 'GetAgentAlias', + 'GetAgent', + 'GetAgentVersion', + 'ListAgentActionGroups', + 'ListAgentAliases', + 'ListAgentKnowledgeBases', + 'ListAgentVersions', + 'PrepareAgent', + 'UpdateAgentActionGroup', + 'UpdateAgentAlias', + 'UpdateAgent', +]; + +const KNOWLEDGE_BASE_OPERATIONS = [ + 'AssociateAgentKnowledgeBase', + 'CreateDataSource', + 'DeleteKnowledgeBase', + 'DisassociateAgentKnowledgeBase', + 'GetAgentKnowledgeBase', + 'GetKnowledgeBase', + 'ListDataSources', + 'UpdateAgentKnowledgeBase', +]; + +const DATA_SOURCE_OPERATIONS = ['DeleteDataSource', 'GetDataSource', 'UpdateDataSource']; + +// The following constants map the way we present the data in telemetry to how they appear in request/responses +// e.g. we put `aws.bedrock.knowledge_base.id` into trace data by finding `knowledgeBaseId` +const agentOperationAttributeKeyMapping = { [AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID]: AGENT_ID }; +const knowledgeBaseOperationAttributeKeyMapping = { + [AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]: KNOWLEDGE_BASE_ID, +}; +const dataSourceOperationAttributeKeyMapping = { + [AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]: DATA_SOURCE_ID, +}; + +// This map allows us to get all relevant attribute key mappings for a given operation +const operationToBedrockAgentAttributesMap: { [key: string]: { [key: string]: string } } = {}; +for (const operation of AGENT_OPERATIONS) { + operationToBedrockAgentAttributesMap[operation] = agentOperationAttributeKeyMapping; +} +for (const operation of KNOWLEDGE_BASE_OPERATIONS) { + operationToBedrockAgentAttributesMap[operation] = knowledgeBaseOperationAttributeKeyMapping; +} +for (const operation of DATA_SOURCE_OPERATIONS) { + operationToBedrockAgentAttributesMap[operation] = dataSourceOperationAttributeKeyMapping; +} + +// This class is an extension for +// Agents for Amazon Bedrock. +// This class primarily identify three types of resource based operations: AGENT_OPERATIONS, +// KNOWLEDGE_BASE_OPERATIONS, and DATA_SOURCE_OPERATIONS. We only support operations that are related to +// the resource and where the context contains the resource ID. +export class BedrockAgentServiceExtension implements ServiceExtension { + requestPreSpanHook( + request: NormalizedRequest, + config: AwsSdkInstrumentationConfig, + diag: DiagLogger + ): RequestMetadata { + const spanAttributes: Attributes = {}; + const isIncoming = false; + const spanKind: SpanKind = SpanKind.CLIENT; + let spanName: string | undefined; + + const operation: string = request.commandName; + if (operation && operationToBedrockAgentAttributesMap[operation]) { + const bedrockAgentServiceInfo = operationToBedrockAgentAttributesMap[operation]; + for (const serviceInfo of Object.entries(bedrockAgentServiceInfo)) { + const [attributeKey, requestParamKey] = serviceInfo; + const requestParamValue = request.commandInput?.[requestParamKey]; + + if (requestParamValue) { + spanAttributes[attributeKey] = requestParamValue; + } + } + } + + return { + isIncoming, + spanAttributes, + spanKind, + spanName, + }; + } + + responseHook(response: NormalizedResponse, span: Span, tracer: Tracer, config: AwsSdkInstrumentationConfig): void { + const operation: string = response.request.commandName; + if (operation && operationToBedrockAgentAttributesMap[operation]) { + const bedrockAgentServiceInfo = operationToBedrockAgentAttributesMap[operation]; + for (const serviceInfo of Object.entries(bedrockAgentServiceInfo)) { + const [attributeKey, responseParamKey] = serviceInfo; + const responseParamValue = response.data[responseParamKey]; + + if (responseParamValue) { + span.setAttribute(attributeKey, responseParamValue); + } + } + } + } +} + +// This class is an extension for +// Agents for Amazon Bedrock Runtime. +export class BedrockAgentRuntimeServiceExtension implements ServiceExtension { + requestPreSpanHook( + request: NormalizedRequest, + config: AwsSdkInstrumentationConfig, + diag: DiagLogger + ): RequestMetadata { + const spanAttributes: Attributes = {}; + const isIncoming = false; + const spanKind: SpanKind = SpanKind.CLIENT; + let spanName: string | undefined; + + const agentId = request.commandInput?.[AGENT_ID]; + const knowledgeBaseId = request.commandInput?.[KNOWLEDGE_BASE_ID]; + + if (agentId) { + spanAttributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID] = agentId; + } + if (knowledgeBaseId) { + spanAttributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID] = knowledgeBaseId; + } + + return { + isIncoming, + spanAttributes, + spanKind, + spanName, + }; + } +} + +// This class is an extension for Bedrock. +export class BedrockServiceExtension implements ServiceExtension { + // Must be implemented, returning empty metadata + requestPreSpanHook( + request: NormalizedRequest, + config: AwsSdkInstrumentationConfig, + diag: DiagLogger + ): RequestMetadata { + const spanAttributes: Attributes = {}; + const isIncoming = false; + const spanKind: SpanKind = SpanKind.CLIENT; + let spanName: string | undefined; + return { + isIncoming, + spanAttributes, + spanKind, + spanName, + }; + } + responseHook(response: NormalizedResponse, span: Span, tracer: Tracer, config: AwsSdkInstrumentationConfig): void { + const guardrail_id = response.data[GUARDRAIL_ID]; + + if (guardrail_id) { + span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID, guardrail_id); + } + } +} + +// This class is an extension for +// Amazon Bedrock Runtime. +export class BedrockRuntimeServiceExtension implements ServiceExtension { + requestPreSpanHook( + request: NormalizedRequest, + config: AwsSdkInstrumentationConfig, + diag: DiagLogger + ): RequestMetadata { + const spanAttributes: Attributes = {}; + const isIncoming = false; + const spanKind: SpanKind = SpanKind.CLIENT; + let spanName: string | undefined; + + const modelId = request.commandInput?.[MODEL_ID]; + + spanAttributes[AwsSpanProcessingUtil.GEN_AI_SYSTEM] = AWS_BEDROCK_SYSTEM; + if (modelId) { + spanAttributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL] = modelId; + } + + return { + isIncoming, + spanAttributes, + spanKind, + spanName, + }; + } +} diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/instrumentation-patch.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/instrumentation-patch.ts index 47ba01c4..776d4c3d 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/instrumentation-patch.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/patches/instrumentation-patch.ts @@ -18,6 +18,12 @@ import { AWSXRAY_TRACE_ID_HEADER, AWSXRayPropagator } from '@opentelemetry/propa import { APIGatewayProxyEventHeaders, Context } from 'aws-lambda'; import { AWS_ATTRIBUTE_KEYS } from '../aws-attribute-keys'; import { RequestMetadata } from '../third-party/otel/aws/services/ServiceExtension'; +import { + BedrockAgentRuntimeServiceExtension, + BedrockAgentServiceExtension, + BedrockRuntimeServiceExtension, + BedrockServiceExtension, +} from './aws/services/bedrock'; import { KinesisServiceExtension } from './aws/services/kinesis'; import { S3ServiceExtension } from './aws/services/s3'; @@ -51,6 +57,10 @@ export function applyInstrumentationPatches(instrumentations: Instrumentation[]) if (services) { services.set('S3', new S3ServiceExtension()); services.set('Kinesis', new KinesisServiceExtension()); + services.set('Bedrock', new BedrockServiceExtension()); + services.set('BedrockAgent', new BedrockAgentServiceExtension()); + services.set('BedrockAgentRuntime', new BedrockAgentRuntimeServiceExtension()); + services.set('BedrockRuntime', new BedrockRuntimeServiceExtension()); patchSqsServiceExtension(services.get('SQS')); } } else if (instrumentation.instrumentationName === '@opentelemetry/instrumentation-aws-lambda') { diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/aws-metric-attribute-generator.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/aws-metric-attribute-generator.test.ts index 69e24423..e74eda35 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/test/aws-metric-attribute-generator.test.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/aws-metric-attribute-generator.test.ts @@ -33,6 +33,7 @@ import { expect } from 'expect'; import { AWS_ATTRIBUTE_KEYS } from '../src/aws-attribute-keys'; import { AwsMetricAttributeGenerator } from '../src/aws-metric-attribute-generator'; import { AttributeMap, DEPENDENCY_METRIC, SERVICE_METRIC } from '../src/metric-attribute-generator'; +import { AwsSpanProcessingUtil } from '../src/aws-span-processing-util'; // Does not exist in @opentelemetry/semantic-conventions const _SERVER_SOCKET_ADDRESS: string = 'server.socket.address'; @@ -784,6 +785,56 @@ describe('AwsMetricAttributeGeneratorTest', () => { mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_DYNAMODB_TABLE_NAMES, ['aws_table^name']); validateRemoteResourceAttributes('AWS::DynamoDB::Table', 'aws_table^^name'); mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_DYNAMODB_TABLE_NAMES, undefined); + + // Validate behaviour of AWS_BEDROCK_AGENT_ID attribute, then remove it. + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID, 'test_agent_id'); + validateRemoteResourceAttributes('AWS::Bedrock::Agent', 'test_agent_id'); + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_DYNAMODB_TABLE_NAMES, undefined); + + // Validate behaviour of AWS_BEDROCK_AGENT_ID attribute with special chars(^), then remove it. + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID, 'test_agent_^id'); + validateRemoteResourceAttributes('AWS::Bedrock::Agent', 'test_agent_^^id'); + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID, undefined); + + // Validate behaviour of AWS_BEDROCK_DATA_SOURCE_ID attribute, then remove it. + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID, 'test_datasource_id'); + validateRemoteResourceAttributes('AWS::Bedrock::DataSource', 'test_datasource_id'); + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID, undefined); + + // Validate behaviour of AWS_BEDROCK_DATA_SOURCE_ID attribute with special chars(^), then remove it. + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID, 'test_datasource_^id'); + validateRemoteResourceAttributes('AWS::Bedrock::DataSource', 'test_datasource_^^id'); + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID, undefined); + + // Validate behaviour of AWS_BEDROCK_GUARDRAIL_ID attribute, then remove it. + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID, 'test_guardrail_id'); + validateRemoteResourceAttributes('AWS::Bedrock::Guardrail', 'test_guardrail_id'); + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID, undefined); + + // Validate behaviour of AWS_BEDROCK_GUARDRAIL_ID attribute with special chars(^), then remove it. + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID, 'test_guardrail_^id'); + validateRemoteResourceAttributes('AWS::Bedrock::Guardrail', 'test_guardrail_^^id'); + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID, undefined); + + // Validate behaviour of AWS_BEDROCK_KNOWLEDGE_BASE_ID attribute, then remove it. + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID, 'test_knowledgeBase_id'); + validateRemoteResourceAttributes('AWS::Bedrock::KnowledgeBase', 'test_knowledgeBase_id'); + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID, undefined); + + // Validate behaviour of AWS_BEDROCK_KNOWLEDGE_BASE_ID attribute with special chars(^), then remove it. + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID, 'test_knowledgeBase_^id'); + validateRemoteResourceAttributes('AWS::Bedrock::KnowledgeBase', 'test_knowledgeBase_^^id'); + mockAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID, undefined); + + // Validate behaviour of GEN_AI_REQUEST_MODEL attribute, then remove it. + mockAttribute(AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL, 'test.service_id'); + validateRemoteResourceAttributes('AWS::Bedrock::Model', 'test.service_id'); + mockAttribute(AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL, undefined); + + // Validate behaviour of GEN_AI_REQUEST_MODEL attribute with special chars(^), then remove it. + mockAttribute(AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL, 'test.service_^id'); + validateRemoteResourceAttributes('AWS::Bedrock::Model', 'test.service_^^id'); + mockAttribute(AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL, undefined); }); it('testDBClientSpanWithRemoteResourceAttributes', () => { @@ -1116,6 +1167,10 @@ describe('AwsMetricAttributeGeneratorTest', () => { testAwsSdkServiceNormalization('Kinesis', 'AWS::Kinesis'); testAwsSdkServiceNormalization('S3', 'AWS::S3'); testAwsSdkServiceNormalization('SQS', 'AWS::SQS'); + testAwsSdkServiceNormalization('Bedrock', 'AWS::Bedrock'); + testAwsSdkServiceNormalization('BedrockAgent', 'AWS::Bedrock'); + testAwsSdkServiceNormalization('BedrockAgentRuntime', 'AWS::Bedrock'); + testAwsSdkServiceNormalization('BedrockRuntime', 'AWS::BedrockRuntime'); }); function testAwsSdkServiceNormalization(serviceName: string, expectedRemoteService: string): void { diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/bedrock.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/bedrock.test.ts new file mode 100644 index 00000000..23edc001 --- /dev/null +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/aws/services/bedrock.test.ts @@ -0,0 +1,308 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { getTestSpans, registerInstrumentationTesting } from '@opentelemetry/contrib-test-utils'; +import { AwsInstrumentation } from '@opentelemetry/instrumentation-aws-sdk'; +import { applyInstrumentationPatches } from './../../../../src/patches/instrumentation-patch'; + +const instrumentations: AwsInstrumentation[] = [new AwsInstrumentation()]; +applyInstrumentationPatches(instrumentations); +registerInstrumentationTesting(instrumentations[0]); + +import { Bedrock } from '@aws-sdk/client-bedrock'; +import { BedrockAgent } from '@aws-sdk/client-bedrock-agent'; +import { BedrockAgentRuntime } from '@aws-sdk/client-bedrock-agent-runtime'; +import { BedrockRuntime } from '@aws-sdk/client-bedrock-runtime'; +import * as nock from 'nock'; + +import { SpanKind } from '@opentelemetry/api'; +import { ReadableSpan } from '@opentelemetry/sdk-trace-base'; +import { expect } from 'expect'; +import { AWS_ATTRIBUTE_KEYS } from '../../../../src/aws-attribute-keys'; +import { AwsSpanProcessingUtil } from '../../../../src/aws-span-processing-util'; + +// This file's contents are being contributed to upstream +// - https://github.com/open-telemetry/opentelemetry-js-contrib/pull/2361 + +const region = 'us-east-1'; + +describe('BedrockAgent', () => { + let bedrock: BedrockAgent; + beforeEach(() => { + bedrock = new BedrockAgent({ + region: region, + credentials: { + accessKeyId: 'abcde', + secretAccessKey: 'abcde', + }, + }); + }); + + describe('GetPrompt', () => { + it('adds no info to span', async () => { + const dummyPromptName: string = 'dummy-prompt-name'; + + nock(`https://bedrock-agent.${region}.amazonaws.com`) + .get(`/prompts/${dummyPromptName}`) + .reply(200, { promptIdentifier: dummyPromptName }); + + await bedrock.getPrompt({ promptIdentifier: dummyPromptName }).catch((err: any) => {}); + + const testSpans: ReadableSpan[] = getTestSpans(); + const getPromptSpans: ReadableSpan[] = testSpans.filter((s: ReadableSpan) => { + return s.name === 'BedrockAgent.GetPrompt'; + }); + expect(getPromptSpans.length).toBe(1); + const getPromptSpan = getPromptSpans[0]; + expect(getPromptSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID]).toBeUndefined(); + expect(getPromptSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]).toBeUndefined(); + expect(getPromptSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]).toBeUndefined(); + expect(getPromptSpan.kind).toBe(SpanKind.CLIENT); + }); + }); + + describe('GetAgent', () => { + it('adds agentId to span', async () => { + const dummyAgentId: string = 'ABCDEFGH'; + + nock(`https://bedrock-agent.${region}.amazonaws.com`) + .get(`/agents/${dummyAgentId}`) + .reply(200, { + agentId: dummyAgentId, + request: { + operation: 'GetAgent', + }, + }); + + await bedrock.getAgent({ agentId: dummyAgentId }).catch((err: any) => {}); + + const testSpans: ReadableSpan[] = getTestSpans(); + const getAgentSpans: ReadableSpan[] = testSpans.filter((s: ReadableSpan) => { + return s.name === 'BedrockAgent.GetAgent'; + }); + expect(getAgentSpans.length).toBe(1); + const getAgentSpan = getAgentSpans[0]; + expect(getAgentSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID]).toBe(dummyAgentId); + expect(getAgentSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]).toBeUndefined(); + expect(getAgentSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]).toBeUndefined(); + expect(getAgentSpan.kind).toBe(SpanKind.CLIENT); + }); + }); + + describe('GetKnowledgeBase', () => { + it('adds knowledgeBaseId to span', async () => { + const dummyKnowledgeBaseId: string = 'ABCDEFGH'; + + nock(`https://bedrock-agent.${region}.amazonaws.com`) + .get(`/knowledgebases/${dummyKnowledgeBaseId}`) + .reply(200, { knowledgeBaseId: dummyKnowledgeBaseId }); + + await bedrock.getKnowledgeBase({ knowledgeBaseId: dummyKnowledgeBaseId }).catch((err: any) => {}); + + const testSpans: ReadableSpan[] = getTestSpans(); + const getKnowledgeBaseSpans: ReadableSpan[] = testSpans.filter((s: ReadableSpan) => { + return s.name === 'BedrockAgent.GetKnowledgeBase'; + }); + expect(getKnowledgeBaseSpans.length).toBe(1); + const getKnowledgeBaseSpan = getKnowledgeBaseSpans[0]; + expect(getKnowledgeBaseSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID]).toBeUndefined(); + expect(getKnowledgeBaseSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]).toBe( + dummyKnowledgeBaseId + ); + expect(getKnowledgeBaseSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]).toBeUndefined(); + expect(getKnowledgeBaseSpan.kind).toBe(SpanKind.CLIENT); + }); + }); + + describe('GetDataSource', () => { + it('adds dataSourceId to span', async () => { + const dummyDataSourceId: string = 'ABCDEFGH'; + const dummyKnowledgeBaseId: string = 'HGFEDCBA'; + + nock(`https://bedrock-agent.${region}.amazonaws.com`) + .get(`/knowledgebases/${dummyKnowledgeBaseId}/datasources/${dummyDataSourceId}`) + .reply(200, { dataSourceId: dummyDataSourceId, knowledgeBaseId: dummyKnowledgeBaseId }); + + await bedrock + .getDataSource({ dataSourceId: dummyDataSourceId, knowledgeBaseId: dummyKnowledgeBaseId }) + .catch((err: any) => {}); + + const testSpans: ReadableSpan[] = getTestSpans(); + const getDataSourceSpans: ReadableSpan[] = testSpans.filter((s: ReadableSpan) => { + return s.name === 'BedrockAgent.GetDataSource'; + }); + expect(getDataSourceSpans.length).toBe(1); + const getDataSourceSpan = getDataSourceSpans[0]; + expect(getDataSourceSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID]).toBeUndefined(); + expect(getDataSourceSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]).toBeUndefined(); + expect(getDataSourceSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]).toBe(dummyDataSourceId); + expect(getDataSourceSpan.kind).toBe(SpanKind.CLIENT); + }); + }); +}); + +describe('BedrockAgentRuntime', () => { + let bedrock: BedrockAgentRuntime; + beforeEach(() => { + bedrock = new BedrockAgentRuntime({ + region: region, + credentials: { + accessKeyId: 'abcde', + secretAccessKey: 'abcde', + }, + }); + }); + + describe('Retrieve', () => { + it('adds knowledgeBaseId to span', async () => { + const dummyKnowledgeBaseId: string = 'ABCDEFGH'; + const dummyQuery: string = 'dummy-query'; + + nock(`https://bedrock-agent-runtime.${region}.amazonaws.com`) + .post(`/knowledgebases/${dummyKnowledgeBaseId}/retrieve`) + .reply(200, { + knowledgeBaseId: dummyKnowledgeBaseId, + retrievalQuery: { text: dummyQuery }, + }); + + await bedrock + .retrieve({ + knowledgeBaseId: dummyKnowledgeBaseId, + retrievalQuery: { text: dummyQuery }, + }) + .catch((err: any) => {}); + + const testSpans: ReadableSpan[] = getTestSpans(); + const retrieveSpans: ReadableSpan[] = testSpans.filter((s: ReadableSpan) => { + return s.name === 'BedrockAgentRuntime.Retrieve'; + }); + expect(retrieveSpans.length).toBe(1); + const retrieveSpan = retrieveSpans[0]; + expect(retrieveSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID]).toBeUndefined(); + expect(retrieveSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]).toBe(dummyKnowledgeBaseId); + expect(retrieveSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]).toBeUndefined(); + expect(retrieveSpan.kind).toBe(SpanKind.CLIENT); + }); + }); + + describe('InvokeAgent', () => { + it('adds agentId to span', async () => { + const dummyAgentId: string = 'ABCDEFGH'; + const dummyAgentAliasId: string = 'HGFEDCBA'; + const dummySessionId: string = 'ABC123AB'; + + nock(`https://bedrock-agent-runtime.${region}.amazonaws.com`) + .post(`/agents/${dummyAgentId}/agentAliases/${dummyAgentAliasId}/sessions/${dummySessionId}/text`) + .reply(200, { + agentId: dummyAgentId, + agentAliasId: dummyAgentAliasId, + sessionId: dummySessionId, + }); + + await bedrock + .invokeAgent({ + agentId: dummyAgentId, + agentAliasId: dummyAgentAliasId, + sessionId: dummySessionId, + }) + .catch((err: any) => {}); + + const testSpans: ReadableSpan[] = getTestSpans(); + const invokeAgentSpans: ReadableSpan[] = testSpans.filter((s: ReadableSpan) => { + return s.name === 'BedrockAgentRuntime.InvokeAgent'; + }); + expect(invokeAgentSpans.length).toBe(1); + const invokeAgentSpan = invokeAgentSpans[0]; + expect(invokeAgentSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID]).toBe(dummyAgentId); + expect(invokeAgentSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]).toBeUndefined(); + expect(invokeAgentSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]).toBeUndefined(); + expect(invokeAgentSpan.kind).toBe(SpanKind.CLIENT); + }); + }); +}); + +describe('Bedrock', () => { + let bedrock: Bedrock; + beforeEach(() => { + bedrock = new Bedrock({ + region: region, + credentials: { + accessKeyId: 'abcde', + secretAccessKey: 'abcde', + }, + }); + }); + + describe('GetGuardrail', () => { + it('adds guardrailId to span', async () => { + const dummyGuardrailIdentifier: string = 'ABCDEFGH'; + + nock(`https://bedrock.${region}.amazonaws.com`).get(`/guardrails/${dummyGuardrailIdentifier}`).reply(200, { + guardrailId: dummyGuardrailIdentifier, + }); + + await bedrock + .getGuardrail({ + guardrailIdentifier: dummyGuardrailIdentifier, + }) + .catch((err: any) => {}); + + const testSpans: ReadableSpan[] = getTestSpans(); + const getGuardrailSpans: ReadableSpan[] = testSpans.filter((s: ReadableSpan) => { + return s.name === 'Bedrock.GetGuardrail'; + }); + expect(getGuardrailSpans.length).toBe(1); + const getGuardrailSpan = getGuardrailSpans[0]; + + expect(getGuardrailSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID]).toBeUndefined(); + expect(getGuardrailSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]).toBeUndefined(); + expect(getGuardrailSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]).toBeUndefined(); + expect(getGuardrailSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID]).toBe(dummyGuardrailIdentifier); + expect(getGuardrailSpan.kind).toBe(SpanKind.CLIENT); + }); + }); +}); + +describe('BedrockRuntime', () => { + let bedrock: BedrockRuntime; + beforeEach(() => { + bedrock = new BedrockRuntime({ + region: region, + credentials: { + accessKeyId: 'abcde', + secretAccessKey: 'abcde', + }, + }); + }); + + describe('InvokeModel', () => { + it('adds modelId to span', async () => { + const dummyModelId: string = 'ABCDEFGH'; + const dummyBody: string = 'HGFEDCBA'; + + nock(`https://bedrock-runtime.${region}.amazonaws.com`).post(`/model/${dummyModelId}/invoke`).reply(200, { + modelId: dummyModelId, + body: dummyBody, + }); + + await bedrock + .invokeModel({ + modelId: dummyModelId, + body: dummyBody, + }) + .catch((err: any) => {}); + + const testSpans: ReadableSpan[] = getTestSpans(); + const invokeModelSpans: ReadableSpan[] = testSpans.filter((s: ReadableSpan) => { + return s.name === 'BedrockRuntime.InvokeModel'; + }); + expect(invokeModelSpans.length).toBe(1); + const invokeModelSpan = invokeModelSpans[0]; + expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID]).toBeUndefined(); + expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]).toBeUndefined(); + expect(invokeModelSpan.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]).toBeUndefined(); + expect(invokeModelSpan.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL]).toBe(dummyModelId); + expect(invokeModelSpan.kind).toBe(SpanKind.CLIENT); + }); + }); +}); diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/instrumentation-patch.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/instrumentation-patch.test.ts index 0265b7da..385c7d75 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/instrumentation-patch.test.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/patches/instrumentation-patch.test.ts @@ -1,10 +1,19 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { Attributes, diag, Context as OtelContext, trace, propagation, Span } from '@opentelemetry/api'; +import { + Attributes, + diag, + Context as OtelContext, + trace, + propagation, + Span, + Tracer, + AttributeValue, +} from '@opentelemetry/api'; import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'; import { Instrumentation } from '@opentelemetry/instrumentation'; -import { AwsInstrumentation, NormalizedRequest } from '@opentelemetry/instrumentation-aws-sdk'; +import { AwsInstrumentation, NormalizedRequest, NormalizedResponse } from '@opentelemetry/instrumentation-aws-sdk'; import { AwsLambdaInstrumentation, AwsLambdaInstrumentationConfig } from '@opentelemetry/instrumentation-aws-lambda'; import { expect } from 'expect'; import { AWS_ATTRIBUTE_KEYS } from '../../src/aws-attribute-keys'; @@ -19,6 +28,12 @@ const _STREAM_NAME: string = 'streamName'; const _BUCKET_NAME: string = 'bucketName'; const _QUEUE_NAME: string = 'queueName'; const _QUEUE_URL: string = 'https://sqs.us-east-1.amazonaws.com/123412341234/queueName'; +const _BEDROCK_AGENT_ID: string = 'agentId'; +const _BEDROCK_DATASOURCE_ID: string = 'DataSourceId'; +const _BEDROCK_GUARDRAIL_ID: string = 'GuardrailId'; +const _BEDROCK_KNOWLEDGEBASE_ID: string = 'KnowledgeBaseId'; +const _GEN_AI_SYSTEM: string = 'aws_bedrock'; +const _GEN_AI_REQUEST_MODEL: string = 'genAiReuqestModelId'; const mockHeaders = { 'x-test-header': 'test-value', @@ -45,6 +60,10 @@ describe('InstrumentationPatchTest', () => { expect(services.has('Kinesis')).toBeFalsy(); expect(services.get('SQS')._requestPreSpanHook).toBeFalsy(); expect(services.get('SQS').requestPreSpanHook).toBeTruthy(); + expect(services.has('Bedrock')).toBeFalsy(); + expect(services.has('BedrockAgent')).toBeFalsy(); + expect(services.get('BedrockAgentRuntime')).toBeFalsy(); + expect(services.get('BedrockRuntime')).toBeFalsy(); }); it('PatchesAwsSdkInstrumentation', () => { @@ -64,6 +83,10 @@ describe('InstrumentationPatchTest', () => { expect(services.has('Kinesis')).toBeTruthy(); expect(services.get('SQS')._requestPreSpanHook).toBeTruthy(); expect(services.get('SQS').requestPreSpanHook).toBeTruthy(); + expect(services.has('Bedrock')).toBeTruthy(); + expect(services.has('BedrockAgent')).toBeTruthy(); + expect(services.get('BedrockAgentRuntime')).toBeTruthy(); + expect(services.get('BedrockRuntime')).toBeTruthy(); // Sanity check expect(services.has('InvalidService')).toBeFalsy(); }); @@ -94,6 +117,12 @@ describe('InstrumentationPatchTest', () => { expect(sqsAttributes[AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_NAME]).toBeUndefined(); }); + it('Bedrock without patching', () => { + const unpatchedAwsSdkInstrumentation: AwsInstrumentation = extractAwsSdkInstrumentation(UNPATCHED_INSTRUMENTATIONS); + const services: Map = extractServicesFromAwsSdkInstrumentation(unpatchedAwsSdkInstrumentation); + expect(() => doExtractBedrockAttributes(services, 'Bedrock')).toThrow(); + }); + it('S3 with patching', () => { const patchedAwsSdkInstrumentation: AwsInstrumentation = extractAwsSdkInstrumentation(PATCHED_INSTRUMENTATIONS); const services: Map = extractServicesFromAwsSdkInstrumentation(patchedAwsSdkInstrumentation); @@ -124,6 +153,88 @@ describe('InstrumentationPatchTest', () => { expect(sqsAttributes[AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_NAME]).toEqual(_QUEUE_NAME); }); + it('Bedrock with patching', () => { + const patchedAwsSdkInstrumentation: AwsInstrumentation = extractAwsSdkInstrumentation(PATCHED_INSTRUMENTATIONS); + const services: Map = extractServicesFromAwsSdkInstrumentation(patchedAwsSdkInstrumentation); + const bedrockAttributes: Attributes = doExtractBedrockAttributes(services, 'Bedrock'); + // Expect no-op from attribute extraction in Bedrock + expect(Object.entries(bedrockAttributes).length).toEqual(0); + const bedrockAttributesAfterResponse: Attributes = doResponseHookBedrock(services, 'Bedrock'); + expect(Object.entries(bedrockAttributesAfterResponse).length).toBe(1); + expect(bedrockAttributesAfterResponse[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID]).toEqual(_BEDROCK_GUARDRAIL_ID); + }); + + it('Bedrock Agent with patching', () => { + const patchedAwsSdkInstrumentation: AwsInstrumentation = extractAwsSdkInstrumentation(PATCHED_INSTRUMENTATIONS); + const services: Map = extractServicesFromAwsSdkInstrumentation(patchedAwsSdkInstrumentation); + + const operation_to_expected_attribute: Object = { + CreateAgentActionGroup: { 'aws.bedrock.agent.id': _BEDROCK_AGENT_ID }, + CreateAgentAlias: { 'aws.bedrock.agent.id': _BEDROCK_AGENT_ID }, + DeleteAgentActionGroup: { 'aws.bedrock.agent.id': _BEDROCK_AGENT_ID }, + DeleteAgentAlias: { 'aws.bedrock.agent.id': _BEDROCK_AGENT_ID }, + DeleteAgent: { 'aws.bedrock.agent.id': _BEDROCK_AGENT_ID }, + DeleteAgentVersion: { 'aws.bedrock.agent.id': _BEDROCK_AGENT_ID }, + GetAgentActionGroup: { 'aws.bedrock.agent.id': _BEDROCK_AGENT_ID }, + GetAgentAlias: { 'aws.bedrock.agent.id': _BEDROCK_AGENT_ID }, + GetAgent: { 'aws.bedrock.agent.id': _BEDROCK_AGENT_ID }, + GetAgentVersion: { 'aws.bedrock.agent.id': _BEDROCK_AGENT_ID }, + ListAgentActionGroups: { 'aws.bedrock.agent.id': _BEDROCK_AGENT_ID }, + ListAgentAliases: { 'aws.bedrock.agent.id': _BEDROCK_AGENT_ID }, + ListAgentKnowledgeBases: { 'aws.bedrock.agent.id': _BEDROCK_AGENT_ID }, + ListAgentVersions: { 'aws.bedrock.agent.id': _BEDROCK_AGENT_ID }, + PrepareAgent: { 'aws.bedrock.agent.id': _BEDROCK_AGENT_ID }, + UpdateAgentActionGroup: { 'aws.bedrock.agent.id': _BEDROCK_AGENT_ID }, + UpdateAgentAlias: { 'aws.bedrock.agent.id': _BEDROCK_AGENT_ID }, + UpdateAgent: { 'aws.bedrock.agent.id': _BEDROCK_AGENT_ID }, + AssociateAgentKnowledgeBase: { 'aws.bedrock.knowledge_base.id': _BEDROCK_KNOWLEDGEBASE_ID }, + CreateDataSource: { 'aws.bedrock.knowledge_base.id': _BEDROCK_KNOWLEDGEBASE_ID }, + DeleteKnowledgeBase: { 'aws.bedrock.knowledge_base.id': _BEDROCK_KNOWLEDGEBASE_ID }, + DisassociateAgentKnowledgeBase: { 'aws.bedrock.knowledge_base.id': _BEDROCK_KNOWLEDGEBASE_ID }, + GetAgentKnowledgeBase: { 'aws.bedrock.knowledge_base.id': _BEDROCK_KNOWLEDGEBASE_ID }, + GetKnowledgeBase: { 'aws.bedrock.knowledge_base.id': _BEDROCK_KNOWLEDGEBASE_ID }, + ListDataSources: { 'aws.bedrock.knowledge_base.id': _BEDROCK_KNOWLEDGEBASE_ID }, + UpdateAgentKnowledgeBase: { 'aws.bedrock.knowledge_base.id': _BEDROCK_KNOWLEDGEBASE_ID }, + DeleteDataSource: { 'aws.bedrock.data_source.id': _BEDROCK_DATASOURCE_ID }, + GetDataSource: { 'aws.bedrock.data_source.id': _BEDROCK_DATASOURCE_ID }, + UpdateDataSource: { 'aws.bedrock.data_source.id': _BEDROCK_DATASOURCE_ID }, + }; + + for (const [operation, attribute_tuple] of Object.entries(operation_to_expected_attribute)) { + const bedrockAttributes: Attributes = doExtractBedrockAttributes(services, 'BedrockAgent', operation); + const [attribute_key, attribute_value] = Object.entries(attribute_tuple)[0]; + expect(Object.entries(bedrockAttributes).length).toBe(1); + expect(bedrockAttributes[attribute_key]).toEqual(attribute_value); + const bedrockAgentSuccessAttributes: Attributes = doResponseHookBedrock(services, 'BedrockAgent', operation); + expect(Object.entries(bedrockAgentSuccessAttributes).length).toBe(1); + expect(bedrockAgentSuccessAttributes[attribute_key]).toEqual(attribute_value); + } + }); + + it('Bedrock Agent Runtime with patching', () => { + const patchedAwsSdkInstrumentation: AwsInstrumentation = extractAwsSdkInstrumentation(PATCHED_INSTRUMENTATIONS); + const services: Map = extractServicesFromAwsSdkInstrumentation(patchedAwsSdkInstrumentation); + const bedrockAttributes: Attributes = doExtractBedrockAttributes(services, 'BedrockAgentRuntime'); + expect(Object.entries(bedrockAttributes).length).toBe(2); + expect(bedrockAttributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID]).toEqual(_BEDROCK_AGENT_ID); + expect(bedrockAttributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]).toEqual(_BEDROCK_KNOWLEDGEBASE_ID); + const bedrockAttributesAfterResponse: Attributes = doResponseHookBedrock(services, 'BedrockAgentRuntime'); + expect(Object.entries(bedrockAttributesAfterResponse).length).toBe(0); + }); + + it('Bedrock Runtime with patching', () => { + const patchedAwsSdkInstrumentation: AwsInstrumentation = extractAwsSdkInstrumentation(PATCHED_INSTRUMENTATIONS); + const services: Map = extractServicesFromAwsSdkInstrumentation(patchedAwsSdkInstrumentation); + const bedrockAttributes: Attributes = doExtractBedrockAttributes(services, 'BedrockRuntime'); + + expect(Object.entries(bedrockAttributes).length).toBe(2); + expect(bedrockAttributes['gen_ai.system']).toEqual(_GEN_AI_SYSTEM); + expect(bedrockAttributes['gen_ai.request.model']).toEqual(_GEN_AI_REQUEST_MODEL); + + const bedrockAttributesAfterResponse: Attributes = doResponseHookBedrock(services, 'BedrockRuntime'); + expect(Object.entries(bedrockAttributesAfterResponse).length).toBe(0); + }); + it('Lambda with custom eventContextExtractor patching', () => { const patchedAwsSdkInstrumentation: AwsLambdaInstrumentation = extractLambdaInstrumentation(PATCHED_INSTRUMENTATIONS); @@ -192,6 +303,25 @@ describe('InstrumentationPatchTest', () => { return doExtractAttributes(services, serviceName, params); } + function doExtractBedrockAttributes( + services: Map, + serviceName: string, + operation?: string + ): Attributes { + const params: NormalizedRequest = { + serviceName: serviceName, + commandName: operation ? operation : 'mockCommandName', + commandInput: { + agentId: _BEDROCK_AGENT_ID, + dataSourceId: _BEDROCK_DATASOURCE_ID, + knowledgeBaseId: _BEDROCK_KNOWLEDGEBASE_ID, + guardrailId: _BEDROCK_GUARDRAIL_ID, + modelId: _GEN_AI_REQUEST_MODEL, + }, + }; + return doExtractAttributes(services, serviceName, params); + } + function doExtractAttributes( services: Map, serviceName: string, @@ -205,6 +335,52 @@ describe('InstrumentationPatchTest', () => { return requestMetadata.spanAttributes || {}; } + function doResponseHookBedrock( + services: Map, + serviceName: string, + operation?: string + ): Attributes { + const results: Partial = { + data: { + agentId: _BEDROCK_AGENT_ID, + dataSourceId: _BEDROCK_DATASOURCE_ID, + knowledgeBaseId: _BEDROCK_KNOWLEDGEBASE_ID, + guardrailId: _BEDROCK_GUARDRAIL_ID, + modelId: _GEN_AI_REQUEST_MODEL, + }, + request: { + commandInput: {}, + commandName: operation || 'dummy_operation', + serviceName: serviceName, + }, + }; + + return doResponseHook(services, serviceName, results as NormalizedResponse); + } + + function doResponseHook( + services: Map, + serviceName: string, + params: NormalizedResponse, + operation?: string + ): Attributes { + const serviceExtension: ServiceExtension = services.get(serviceName)!; + if (serviceExtension === undefined) { + throw new Error(`serviceExtension for ${serviceName} is not defined in the provided Map of services`); + } + + const spanAttributes: Attributes = {}; + const mockSpan: Partial = {}; + // Make span update test version of span attributes + mockSpan.setAttribute = (key: string, value: AttributeValue) => { + spanAttributes[key] = value; + return mockSpan as Span; + }; + serviceExtension.responseHook?.(params, mockSpan as Span, {} as Tracer, {}); + + return spanAttributes; + } + function extractLambdaInstrumentation(instrumentations: Instrumentation[]): AwsLambdaInstrumentation { const filteredInstrumentations: Instrumentation[] = instrumentations.filter( instrumentation => instrumentation.instrumentationName === '@opentelemetry/instrumentation-aws-lambda' diff --git a/package-lock.json b/package-lock.json index ec261d79..351bfa73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,6 +57,10 @@ "@opentelemetry/semantic-conventions": "1.27.0" }, "devDependencies": { + "@aws-sdk/client-bedrock": "3.632.0", + "@aws-sdk/client-bedrock-agent": "3.632.0", + "@aws-sdk/client-bedrock-agent-runtime": "3.632.0", + "@aws-sdk/client-bedrock-runtime": "3.632.0", "@aws-sdk/client-kinesis": "3.632.0", "@aws-sdk/client-s3": "3.632.0", "@aws-sdk/client-sqs": "3.632.0", @@ -77,6 +81,167 @@ "node": ">=14" } }, + "aws-distro-opentelemetry-node-autoinstrumentation/node_modules/@aws-sdk/client-bedrock": { + "version": "3.632.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock/-/client-bedrock-3.632.0.tgz", + "integrity": "sha512-MSzhFQ1RqSJ/t6bqFZTqm6rGq/g/qLwacn3zy0p2b+hKwAyUZH0sPJMisPhslm6Mm2NyNWraQoY1tRdpgWxXkw==", + "dev": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.632.0", + "@aws-sdk/client-sts": "3.632.0", + "@aws-sdk/core": "3.629.0", + "@aws-sdk/credential-provider-node": "3.632.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.632.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.632.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.2", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.14", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.14", + "@smithy/util-defaults-mode-node": "^3.0.14", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "aws-distro-opentelemetry-node-autoinstrumentation/node_modules/@aws-sdk/client-bedrock-agent": { + "version": "3.632.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-agent/-/client-bedrock-agent-3.632.0.tgz", + "integrity": "sha512-09zZFgL870USDvUtjnFEep6usI8y1BHP5KCyv5OSDr0ZDLOmCs2hvFa7aWVryjOl1niPg7cDuQUT6ga4L1Td3Q==", + "dev": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.632.0", + "@aws-sdk/client-sts": "3.632.0", + "@aws-sdk/core": "3.629.0", + "@aws-sdk/credential-provider-node": "3.632.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.632.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.632.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.2", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.14", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.14", + "@smithy/util-defaults-mode-node": "^3.0.14", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "aws-distro-opentelemetry-node-autoinstrumentation/node_modules/@aws-sdk/client-bedrock-agent-runtime": { + "version": "3.632.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-agent-runtime/-/client-bedrock-agent-runtime-3.632.0.tgz", + "integrity": "sha512-oA2O04motlB74YjIPXWPOOrnlL6LwpJOcCbv0ghc+MirptKN+a9ZagwHA1nbr4/lwvyVH1N6om+OrgIIDkNKiA==", + "dev": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.632.0", + "@aws-sdk/client-sts": "3.632.0", + "@aws-sdk/core": "3.629.0", + "@aws-sdk/credential-provider-node": "3.632.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.632.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.632.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.2", + "@smithy/eventstream-serde-browser": "^3.0.6", + "@smithy/eventstream-serde-config-resolver": "^3.0.3", + "@smithy/eventstream-serde-node": "^3.0.5", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.14", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.14", + "@smithy/util-defaults-mode-node": "^3.0.14", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "aws-distro-opentelemetry-node-autoinstrumentation/node_modules/typescript": { "version": "4.4.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", @@ -91,6 +256,19 @@ "node": ">=4.2.0" } }, + "aws-distro-opentelemetry-node-autoinstrumentation/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -323,6 +501,62 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/client-bedrock-runtime": { + "version": "3.632.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.632.0.tgz", + "integrity": "sha512-iODVrsWvAaBOdXWrSxEajD2Hq1N94AJ2PBDiaDUjOwNg+TkGiR1ifSil4b0xFhNHUYhE76T5kJXOvdY+rTYwnw==", + "dev": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.632.0", + "@aws-sdk/client-sts": "3.632.0", + "@aws-sdk/core": "3.629.0", + "@aws-sdk/credential-provider-node": "3.632.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.632.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.632.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.3.2", + "@smithy/eventstream-serde-browser": "^3.0.6", + "@smithy/eventstream-serde-config-resolver": "^3.0.3", + "@smithy/eventstream-serde-node": "^3.0.5", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.14", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.1.12", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.14", + "@smithy/util-defaults-mode-node": "^3.0.14", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/client-kinesis": { "version": "3.632.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-kinesis/-/client-kinesis-3.632.0.tgz", @@ -5388,7 +5622,6 @@ "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.4.tgz", "integrity": "sha512-VupaALAQlXViW3/enTf/f5l5JZYSAxoJL7f0nanhNNKnww6DGCg1oYIuNP78KDugnkwthBO6iEcym16HhWV8RQ==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.4.2", "tslib": "^2.6.2" @@ -5423,7 +5656,6 @@ "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.8.tgz", "integrity": "sha512-Tv1obAC18XOd2OnDAjSWmmthzx6Pdeh63FbLin8MlPiuJ2ATpKkq0NcNOJFr0dO+JmZXnwu8FQxKJ3TKJ3Hulw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.7", "@smithy/types": "^3.4.2", @@ -5440,7 +5672,6 @@ "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.4.3.tgz", "integrity": "sha512-4LTusLqFMRVQUfC3RNuTg6IzYTeJNpydRdTKq7J5wdEyIRQSu3rGIa3s80mgG2hhe6WOZl9IqTSo1pgbn6EHhA==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/middleware-endpoint": "^3.1.3", "@smithy/middleware-retry": "^3.0.18", @@ -5462,7 +5693,6 @@ "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.3.tgz", "integrity": "sha512-VoxMzSzdvkkjMJNE38yQgx4CfnmT+Z+5EUXkg4x7yag93eQkVQgZvN3XBSHC/ylfBbLbAtdu7flTCChX9I+mVg==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.7", "@smithy/property-provider": "^3.1.6", @@ -5479,7 +5709,6 @@ "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.5.tgz", "integrity": "sha512-6pu+PT2r+5ZnWEV3vLV1DzyrpJ0TmehQlniIDCSpZg6+Ji2SfOI38EqUyQ+O8lotVElCrfVc9chKtSMe9cmCZQ==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^3.4.2", @@ -5492,7 +5721,6 @@ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.9.tgz", "integrity": "sha512-PiQLo6OQmZAotJweIcObL1H44gkvuJACKMNqpBBe5Rf2Ax1DOcGi/28+feZI7yTe1ERHlQQaGnm8sSkyDUgsMg==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/eventstream-serde-universal": "^3.0.8", "@smithy/types": "^3.4.2", @@ -5507,7 +5735,6 @@ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.6.tgz", "integrity": "sha512-iew15It+c7WfnVowWkt2a7cdPp533LFJnpjDQgfZQcxv2QiOcyEcea31mnrk5PVbgo0nNH3VbYGq7myw2q/F6A==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.4.2", "tslib": "^2.6.2" @@ -5521,7 +5748,6 @@ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.8.tgz", "integrity": "sha512-6m+wI+fT0na+6oao6UqALVA38fsScCpoG5UO/A8ZSyGLnPM2i4MS1cFUhpuALgvLMxfYoTCh7qSeJa0aG4IWpQ==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/eventstream-serde-universal": "^3.0.8", "@smithy/types": "^3.4.2", @@ -5536,7 +5762,6 @@ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.8.tgz", "integrity": "sha512-09tqzIQ6e+7jLqGvRji1yJoDbL/zob0OFhq75edgStWErGLf16+yI5hRc/o9/YAybOhUZs/swpW2SPn892G5Gg==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/eventstream-codec": "^3.1.5", "@smithy/types": "^3.4.2", @@ -5551,7 +5776,6 @@ "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.7.tgz", "integrity": "sha512-Ra6IPI1spYLO+t62/3jQbodjOwAbto9wlpJdHZwkycm0Kit+GVpzHW/NMmSgY4rK1bjJ4qLAmCnaBzePO5Nkkg==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/protocol-http": "^4.1.3", "@smithy/querystring-builder": "^3.0.6", @@ -5578,7 +5802,6 @@ "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.6.tgz", "integrity": "sha512-c/FHEdKK/7DU2z6ZE91L36ahyXWayR3B+FzELjnYq7wH5YqIseM24V+pWCS9kFn1Ln8OFGTf+pyYPiHZuX0s/Q==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.4.2", "@smithy/util-buffer-from": "^3.0.0", @@ -5609,7 +5832,6 @@ "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.6.tgz", "integrity": "sha512-czM7Ioq3s8pIXht7oD+vmgy4Wfb4XavU/k/irO8NdXFFOx7YAlsCCcKOh/lJD1mJSYQqiR7NmpZ9JviryD/7AQ==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.4.2", "tslib": "^2.6.2" @@ -5645,7 +5867,6 @@ "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.8.tgz", "integrity": "sha512-VuyszlSO49WKh3H9/kIO2kf07VUwGV80QRiaDxUfP8P8UKlokz381ETJvwLhwuypBYhLymCYyNhB3fLAGBX2og==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/protocol-http": "^4.1.3", "@smithy/types": "^3.4.2", @@ -5660,7 +5881,6 @@ "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.3.tgz", "integrity": "sha512-KeM/OrK8MVFUsoJsmCN0MZMVPjKKLudn13xpgwIMpGTYpA8QZB2Xq5tJ+RE6iu3A6NhOI4VajDTwBsm8pwwrhg==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/middleware-serde": "^3.0.6", "@smithy/node-config-provider": "^3.1.7", @@ -5679,7 +5899,6 @@ "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.18.tgz", "integrity": "sha512-YU1o/vYob6vlqZdd97MN8cSXRToknLXhFBL3r+c9CZcnxkO/rgNZ++CfgX2vsmnEKvlqdi26+SRtSzlVp5z6Mg==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.7", "@smithy/protocol-http": "^4.1.3", @@ -5700,7 +5919,6 @@ "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.6.tgz", "integrity": "sha512-KKTUSl1MzOM0MAjGbudeaVNtIDo+PpekTBkCNwvfZlKndodrnvRo+00USatiyLOc0ujjO9UydMRu3O9dYML7ag==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.4.2", "tslib": "^2.6.2" @@ -5714,7 +5932,6 @@ "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.6.tgz", "integrity": "sha512-2c0eSYhTQ8xQqHMcRxLMpadFbTXg6Zla5l0mwNftFCZMQmuhI7EbAJMx6R5eqfuV3YbJ3QGyS3d5uSmrHV8Khg==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.4.2", "tslib": "^2.6.2" @@ -5728,7 +5945,6 @@ "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.7.tgz", "integrity": "sha512-g3mfnC3Oo8pOI0dYuPXLtdW1WGVb3bR2tkV21GNkm0ZvQjLTtamXAwCWt/FCb0HGvKt3gHHmF1XerG0ICfalOg==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^3.1.6", "@smithy/shared-ini-file-loader": "^3.1.7", @@ -5744,7 +5960,6 @@ "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.2.tgz", "integrity": "sha512-42Cy4/oT2O+00aiG1iQ7Kd7rE6q8j7vI0gFfnMlUiATvyo8vefJkhb7O10qZY0jAqo5WZdUzfl9IV6wQ3iMBCg==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^3.1.4", "@smithy/protocol-http": "^4.1.3", @@ -5761,7 +5976,6 @@ "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.6.tgz", "integrity": "sha512-NK3y/T7Q/Bw+Z8vsVs9MYIQ5v7gOX7clyrXcwhhIBQhbPgRl6JDrZbusO9qWDhcEus75Tg+VCxtIRfo3H76fpw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.4.2", "tslib": "^2.6.2" @@ -5775,7 +5989,6 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.4.2", "tslib": "^2.6.2" @@ -5789,7 +6002,6 @@ "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.6.tgz", "integrity": "sha512-sQe08RunoObe+Usujn9+R2zrLuQERi3CWvRO3BvnoWSYUaIrLKuAIeY7cMeDax6xGyfIP3x/yFWbEKSXvOnvVg==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.4.2", "@smithy/util-uri-escape": "^3.0.0", @@ -5804,7 +6016,6 @@ "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.6.tgz", "integrity": "sha512-UJKw4LlEkytzz2Wq+uIdHf6qOtFfee/o7ruH0jF5I6UAuU+19r9QV7nU3P/uI0l6+oElRHmG/5cBBcGJrD7Ozg==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.4.2", "tslib": "^2.6.2" @@ -5818,7 +6029,6 @@ "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.6.tgz", "integrity": "sha512-53SpchU3+DUZrN7J6sBx9tBiCVGzsib2e4sc512Q7K9fpC5zkJKs6Z9s+qbMxSYrkEkle6hnMtrts7XNkMJJMg==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.4.2" }, @@ -5831,7 +6041,6 @@ "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.7.tgz", "integrity": "sha512-IA4K2qTJYXkF5OfVN4vsY1hfnUZjaslEE8Fsr/gGFza4TAC2A9NfnZuSY2srQIbt9bwtjHiAayrRVgKse4Q7fA==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.4.2", "tslib": "^2.6.2" @@ -5845,7 +6054,6 @@ "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.3.tgz", "integrity": "sha512-YD2KYSCEEeFHcWZ1E3mLdAaHl8T/TANh6XwmocQ6nPcTdBfh4N5fusgnblnWDlnlU1/cUqEq3PiGi22GmT2Lkg==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "@smithy/protocol-http": "^4.1.3", @@ -5865,7 +6073,6 @@ "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.3.2.tgz", "integrity": "sha512-RKDfhF2MTwXl7jan5d7QfS9eCC6XJbO3H+EZAvLQN8A5in4ib2Ml4zoeLo57w9QrqFekBPcsoC2hW3Ekw4vQ9Q==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/middleware-endpoint": "^3.1.3", "@smithy/middleware-stack": "^3.0.6", @@ -5883,7 +6090,6 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", "dev": true, - "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -5896,7 +6102,6 @@ "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.6.tgz", "integrity": "sha512-47Op/NU8Opt49KyGpHtVdnmmJMsp2hEwBdyjuFB9M2V5QVOwA7pBhhxKN5z6ztKGrMw76gd8MlbPuzzvaAncuQ==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/querystring-parser": "^3.0.6", "@smithy/types": "^3.4.2", @@ -5973,7 +6178,6 @@ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.18.tgz", "integrity": "sha512-/eveCzU6Z6Yw8dlYQLA4rcK30XY0E4L3lD3QFHm59mzDaWYelrXE1rlynuT3J6qxv+5yNy3a1JuzhG5hk5hcmw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^3.1.6", "@smithy/smithy-client": "^3.3.2", @@ -5990,7 +6194,6 @@ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.18.tgz", "integrity": "sha512-9cfzRjArtOFPlTYRREJk00suUxVXTgbrzVncOyMRTUeMKnecG/YentLF3cORa+R6mUOMSrMSnT18jos1PKqK6Q==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/config-resolver": "^3.0.8", "@smithy/credential-provider-imds": "^3.2.3", @@ -6009,7 +6212,6 @@ "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.2.tgz", "integrity": "sha512-FEISzffb4H8DLzGq1g4MuDpcv6CIG15fXoQzDH9SjpRJv6h7J++1STFWWinilG0tQh9H1v2UKWG19Jjr2B16zQ==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.7", "@smithy/types": "^3.4.2", @@ -6037,7 +6239,6 @@ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.6.tgz", "integrity": "sha512-BxbX4aBhI1O9p87/xM+zWy0GzT3CEVcXFPBRDoHAM+pV0eSW156pR+PSYEz0DQHDMYDsYAflC2bQNz2uaDBUZQ==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.4.2", "tslib": "^2.6.2" @@ -6051,7 +6252,6 @@ "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.6.tgz", "integrity": "sha512-BRZiuF7IwDntAbevqMco67an0Sr9oLQJqqRCsSPZZHYRnehS0LHDAkJk/pSmI7Z8c/1Vet294H7fY2fWUgB+Rg==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/service-error-classification": "^3.0.6", "@smithy/types": "^3.4.2", @@ -6066,7 +6266,6 @@ "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.6.tgz", "integrity": "sha512-lQEUfTx1ht5CRdvIjdAN/gUL6vQt2wSARGGLaBHNe+iJSkRHlWzY+DOn0mFTmTgyU3jcI5n9DkT5gTzYuSOo6A==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@smithy/fetch-http-handler": "^3.2.7", "@smithy/node-http-handler": "^3.2.2",