Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat): Add granular user agent for Ai #13835

Merged
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d95573e
feat: Add ability to override api user agent with granular actions
cshfang Sep 13, 2024
799f178
Removed customUserAgentDetails and introduced new symbol to override …
yuhengshs Sep 20, 2024
df29334
moved symbol to constant file, export AiActions
yuhengshs Sep 20, 2024
d7ad0e3
update symbol to have fallback, added unit test for graphql, consolid…
yuhengshs Sep 20, 2024
820b676
moved symbol to Platform/index.ts
yuhengshs Sep 20, 2024
36b0100
added internal ts-doc for UA Override symbol
yuhengshs Sep 23, 2024
14f440b
added delete conversation as AiAction #8
yuhengshs Sep 24, 2024
90994db
Bump up bundle size, added GraphQLOptionsWithOverride
yuhengshs Sep 25, 2024
7a47e40
Merge branch 'main' into yuhengsh-feat/add-granular-user-agent
yuhengshs Sep 25, 2024
8f5c837
replace GraphQLOptionsWithOverride as type guards
yuhengshs Sep 25, 2024
40d8bdb
import IUAO symbol from data-schema in api package
yuhengshs Sep 26, 2024
0eb4fd8
Merge branch 'main' into yuhengsh-feat/add-granular-user-agent
yuhengshs Sep 26, 2024
a3f988d
bump up data-schma version to suppoert User Agent Override for AiAction
yuhengshs Sep 27, 2024
9c4b98c
bump up generateClient bundle size
yuhengshs Sep 27, 2024
a2734a5
avoid usage of let and shorten unit test to just focus on User Agent …
yuhengshs Sep 27, 2024
2885249
reorganized enum orders for AiAction
yuhengshs Sep 27, 2024
ca1a7f3
update data-schema version
yuhengshs Sep 27, 2024
c1078f9
update yarn.lock
yuhengshs Sep 30, 2024
865847e
Merge branch 'main' into yuhengsh-feat/add-granular-user-agent
yuhengshs Oct 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions packages/api-graphql/__tests__/GraphQLAPI.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import * as typedQueries from './fixtures/with-types/queries';
import * as typedSubscriptions from './fixtures/with-types/subscriptions';
import { expectGet } from './utils/expects';
import { InternalGraphQLAPIClass } from '../src/internals/InternalGraphQLAPI';
import {
INTERNAL_USER_AGENT_OVERRIDE,
GraphQLAuthMode,
} from '@aws-amplify/core/internals/utils';

import {
__amplify,
Expand Down Expand Up @@ -1614,4 +1618,83 @@ describe('API test', () => {
const subscribeOptions = spyon_appsync_realtime.mock.calls[0][0];
expect(subscribeOptions).toBe(resolvedUrl);
});
test('graphql method handles INTERNAL_USER_AGENT_OVERRIDE correctly', async () => {
yuhengshs marked this conversation as resolved.
Show resolved Hide resolved
Amplify.configure({
API: {
GraphQL: {
defaultAuthMode: 'apiKey',
apiKey: 'FAKE-KEY',
endpoint: 'https://localhost/graphql',
region: 'local-host-h4x',
},
},
});
const threadToGet = {
id: 'some-id',
topic: 'something reasonably interesting',
};
const graphqlVariables = { id: 'some-id' };
const graphqlResponse = {
data: {
getThread: {
__typename: 'Thread',
...serverManagedFields,
...threadToGet,
},
},
};
const spy = jest
.spyOn((raw.GraphQLAPI as any)._api, 'post')
.mockReturnValue({
body: {
json: () => graphqlResponse,
},
});
const graphqlOptions: raw.GraphQLOptionsV6<
GetThreadQuery,
typeof typedQueries.getThread
> = {
query: typedQueries.getThread,
variables: graphqlVariables,
authMode: 'apiKey' as GraphQLAuthMode,
};
// Add the INTERNAL_USER_AGENT_OVERRIDE to the options object
(graphqlOptions as any)[INTERNAL_USER_AGENT_OVERRIDE] = {
category: 'CustomCategory',
action: 'CustomAction',
};

const result: GraphQLResult<GetThreadQuery> =
await client.graphql(graphqlOptions);

const thread: GetThreadQuery['getThread'] = result.data?.getThread;
const errors = result.errors;

expectGet(spy, 'getThread', graphqlVariables);
expect(errors).toBe(undefined);
expect(thread).toEqual(graphqlResponse.data.getThread);

// Check if the INTERNAL_USER_AGENT_OVERRIDE was properly handled
expect(spy).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
options: expect.objectContaining({
headers: expect.objectContaining({
'x-amz-user-agent': expect.stringContaining(
'CustomCategory/CustomAction',
),
}),
}),
}),
);
// Ensure the INTERNAL_USER_AGENT_OVERRIDE was not passed along in the options
expect(spy).not.toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
options: expect.objectContaining({
[INTERNAL_USER_AGENT_OVERRIDE]: expect.anything(),
}),
}),
);
});
});
46 changes: 41 additions & 5 deletions packages/api-graphql/src/GraphQLAPI.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { AmplifyClassV6 } from '@aws-amplify/core';
import { ApiAction, Category } from '@aws-amplify/core/internals/utils';
import {
ApiAction,
Category,
CustomUserAgentDetails,
INTERNAL_USER_AGENT_OVERRIDE,
} from '@aws-amplify/core/internals/utils';
import { CustomHeaders } from '@aws-amplify/data-schema/runtime';
import { Observable } from 'rxjs';

import { GraphQLOptions, GraphQLResult } from './types';
import { InternalGraphQLAPIClass } from './internals/InternalGraphQLAPI';

function isGraphQLOptionsWithOverride(
options: GraphQLOptions,
): options is GraphQLOptions & {
[INTERNAL_USER_AGENT_OVERRIDE]: CustomUserAgentDetails;
} {
return INTERNAL_USER_AGENT_OVERRIDE in options;
}

export const graphqlOperation = (
query: any,
variables = {},
Expand Down Expand Up @@ -38,10 +51,33 @@ export class GraphQLAPIClass extends InternalGraphQLAPIClass {
options: GraphQLOptions,
additionalHeaders?: CustomHeaders,
): Observable<GraphQLResult<T>> | Promise<GraphQLResult<T>> {
return super.graphql(amplify, options, additionalHeaders, {
category: Category.API,
action: ApiAction.GraphQl,
});
let cleanOptions = options;
let userAgentDetails: CustomUserAgentDetails;

if (isGraphQLOptionsWithOverride(options)) {
const {
[INTERNAL_USER_AGENT_OVERRIDE]: internalUserAgentOverride,
...rest
} = options;
userAgentDetails = {
yuhengshs marked this conversation as resolved.
Show resolved Hide resolved
category: Category.API,
action: ApiAction.GraphQl,
...internalUserAgentOverride,
};
cleanOptions = rest;
} else {
userAgentDetails = {
category: Category.API,
action: ApiAction.GraphQl,
};
}

return super.graphql(
amplify,
cleanOptions,
additionalHeaders,
userAgentDetails,
);
yuhengshs marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down
20 changes: 10 additions & 10 deletions packages/aws-amplify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@
"name": "[Analytics] record (Pinpoint)",
"path": "./dist/esm/analytics/index.mjs",
"import": "{ record }",
"limit": "17.25 kB"
"limit": "17.35 kB"
},
{
"name": "[Analytics] record (Kinesis)",
Expand All @@ -317,7 +317,7 @@
"name": "[Analytics] identifyUser (Pinpoint)",
"path": "./dist/esm/analytics/index.mjs",
"import": "{ identifyUser }",
"limit": "15.75 kB"
"limit": "15.85 kB"
},
{
"name": "[Analytics] enable",
Expand All @@ -335,7 +335,7 @@
"name": "[API] generateClient (AppSync)",
"path": "./dist/esm/api/index.mjs",
"import": "{ generateClient }",
"limit": "43.1 kB"
"limit": "43.25 kB"
},
{
"name": "[API] REST API handlers",
Expand Down Expand Up @@ -461,43 +461,43 @@
"name": "[Storage] copy (S3)",
"path": "./dist/esm/storage/index.mjs",
"import": "{ copy }",
"limit": "14.86 kB"
"limit": "14.96 kB"
},
{
"name": "[Storage] downloadData (S3)",
"path": "./dist/esm/storage/index.mjs",
"import": "{ downloadData }",
"limit": "15.45 kB"
"limit": "15.55 kB"
},
{
"name": "[Storage] getProperties (S3)",
"path": "./dist/esm/storage/index.mjs",
"import": "{ getProperties }",
"limit": "14.71 kB"
"limit": "14.81 kB"
},
{
"name": "[Storage] getUrl (S3)",
"path": "./dist/esm/storage/index.mjs",
"import": "{ getUrl }",
"limit": "15.95 kB"
"limit": "16.05 kB"
},
{
"name": "[Storage] list (S3)",
"path": "./dist/esm/storage/index.mjs",
"import": "{ list }",
"limit": "15.31 kB"
"limit": "15.41 kB"
},
{
"name": "[Storage] remove (S3)",
"path": "./dist/esm/storage/index.mjs",
"import": "{ remove }",
"limit": "14.57 kB"
"limit": "14.67 kB"
},
{
"name": "[Storage] uploadData (S3)",
"path": "./dist/esm/storage/index.mjs",
"import": "{ uploadData }",
"limit": "19.99 kB"
"limit": "20.05 kB"
}
]
}
12 changes: 12 additions & 0 deletions packages/core/src/Platform/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ class PlatformBuilder {
}
}

/**
* Symbol used for internal user agent overrides.
*
* @internal
* This symbol is intended for internal use within the Amplify library.
* It may change or be removed in future versions without notice.
* External usage of this symbol is discouraged and may lead to unexpected behavior.
*/
export const INTERNAL_USER_AGENT_OVERRIDE = Symbol(
'INTERNAL_USER_AGENT_OVERRIDE',
);

export const Platform = new PlatformBuilder();

export const getAmplifyUserAgentObject = ({
Expand Down
23 changes: 22 additions & 1 deletion packages/core/src/Platform/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export enum Framework {
}

export enum Category {
AI = 'ai',
API = 'api',
Auth = 'auth',
Analytics = 'analytics',
Expand All @@ -39,6 +40,17 @@ export enum Category {
Storage = 'storage',
}

export enum AiAction {
CreateConversation = '1',
GetConversation = '2',
ListConversations = '3',
SendMessage = '4',
ListMessages = '5',
OnMessage = '6',
Generation = '7',
Delete = '8',
yuhengshs marked this conversation as resolved.
Show resolved Hide resolved
}

export enum AnalyticsAction {
Record = '1',
IdentifyUser = '2',
Expand Down Expand Up @@ -123,6 +135,7 @@ export enum StorageAction {
}

interface ActionMap {
[Category.AI]: AiAction;
[Category.Auth]: AuthAction;
[Category.API]: ApiAction;
[Category.Analytics]: AnalyticsAction;
Expand All @@ -148,6 +161,7 @@ interface CustomUserAgentDetailsBase {

export type CustomUserAgentDetails =
| (CustomUserAgentDetailsBase & { category?: never; action?: never })
| UserAgentDetailsWithCategory<Category.AI>
| UserAgentDetailsWithCategory<Category.API>
| UserAgentDetailsWithCategory<Category.Auth>
| UserAgentDetailsWithCategory<Category.Analytics>
Expand Down Expand Up @@ -180,6 +194,12 @@ export interface StorageUserAgentInput {
additionalDetails: AdditionalDetails;
}

export interface AiUserAgentInput {
category: Category.AI;
apis: AiAction[];
additionalDetails: AdditionalDetails;
}

export interface AuthUserAgentInput {
category: Category.Auth;
apis: AuthAction[];
Expand All @@ -202,4 +222,5 @@ export type SetCustomUserAgentInput =
| StorageUserAgentInput
| AuthUserAgentInput
| InAppMessagingUserAgentInput
| GeoUserAgentInput;
| GeoUserAgentInput
| AiUserAgentInput;
2 changes: 2 additions & 0 deletions packages/core/src/libraryUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,13 @@ export { haveCredentialsChanged } from './utils/haveCredentialsChanged';

// Platform & user-agent utilities
export {
INTERNAL_USER_AGENT_OVERRIDE,
Platform,
getAmplifyUserAgentObject,
getAmplifyUserAgent,
} from './Platform';
export {
AiAction,
ApiAction,
AuthAction,
AnalyticsAction,
Expand Down
Loading