Thiết lập monorepo
Nhiệm vụ 1: Tạo monorepo
Phần tiêu đề “Nhiệm vụ 1: Tạo monorepo”Để tạo monorepo mới, từ thư mục mong muốn của bạn, chạy lệnh sau:
pnpm create @aws/nx-workspace dungeon-adventure --iac=cdkyarn create @aws/nx-workspace dungeon-adventure --iac=cdknpm create @aws/nx-workspace -- dungeon-adventure --iac=cdkbun create @aws/nx-workspace dungeon-adventure --iac=cdkLệnh này sẽ thiết lập một NX monorepo trong thư mục dungeon-adventure. Khi bạn mở thư mục trong VSCode, bạn sẽ thấy cấu trúc tệp này:
Thư mục.nx/
- …
Thư mục.vscode/
- …
Thư mụcnode_modules/
- …
Thư mụcpackages/ this is where your sub-projects will reside
- …
- .gitignore
- biome.json configures Biome for linting and formatting
- nx.json configures the Nx CLI and monorepo defaults
- package.json all node dependencies are defined here
- pnpm-lock.yaml or bun.lock, yarn.lock, package-lock.json depending on package manager
- pnpm-workspace.yaml if using pnpm
- README.md
- tsconfig.base.json all node based sub-projects extend this
- tsconfig.json
- aws-nx-plugin.config.mts configuraton for the Nx Plugin for AWS
Nhiệm vụ 2: Khởi tạo Dungeon Adventure Game
Phần tiêu đề “Nhiệm vụ 2: Khởi tạo Dungeon Adventure Game”Với workspace đã sẵn sàng, chúng ta khởi tạo các dự án con của trò chơi — Game API, Story Agent, Inventory MCP server, cơ sở dữ liệu trò chơi và website — cùng với các kết nối liên kết chúng lại với nhau. Có hai cách để thực hiện:
- Nhanh — sao chép các lệnh trực tiếp từ sơ đồ bên dưới và chạy chúng. Đây là cách nhanh nhất để đạt được điểm khởi đầu tương tự.
- Từng bước — mở rộng phần bên dưới để tự chạy từng generator và kiểm tra chính xác những gì mỗi generator tạo ra.
Sơ đồ bên dưới chính là workspace Dungeon Adventure: mọi dự án, thành phần và kết nối bạn sẽ xây dựng trong module này. Nhấn Copy commands để lấy toàn bộ chuỗi lệnh, sau đó chạy chúng từ trong thư mục dungeon-adventure bạn đã tạo ở Nhiệm vụ 1.
Từng bước
Thay vì sao chép tất cả các lệnh cùng một lúc, bạn có thể chạy từng generator riêng lẻ. Đây là cách tốt nhất để hiểu những gì mỗi generator thêm vào workspace của bạn. Chạy từng generator theo thứ tự, từ trong thư mục dungeon-adventure bạn đã tạo ở Nhiệm vụ 1.
Tạo Game API
Phần tiêu đề “Tạo Game API”Đầu tiên, hãy tạo Game API của chúng ta. Để làm điều này, tạo một tRPC API có tên GameApi bằng các bước sau:
pnpm nx g @aws/nx-plugin:ts#api --name=GameApi --framework=trpc --no-interactiveyarn nx g @aws/nx-plugin:ts#api --name=GameApi --framework=trpc --no-interactivenpx nx g @aws/nx-plugin:ts#api --name=GameApi --framework=trpc --no-interactivebunx nx g @aws/nx-plugin:ts#api --name=GameApi --framework=trpc --no-interactiveBạn cũng có thể thực hiện chạy thử để xem những tệp nào sẽ bị thay đổi
pnpm nx g @aws/nx-plugin:ts#api --name=GameApi --framework=trpc --no-interactive --dry-runyarn nx g @aws/nx-plugin:ts#api --name=GameApi --framework=trpc --no-interactive --dry-runnpx nx g @aws/nx-plugin:ts#api --name=GameApi --framework=trpc --no-interactive --dry-runbunx nx g @aws/nx-plugin:ts#api --name=GameApi --framework=trpc --no-interactive --dry-run- Cài đặt Nx Console VSCode Plugin nếu bạn chưa cài đặt
- Mở Nx Console trong VSCode
- Nhấp
Generate (UI)trong phần "Common Nx Commands" - Tìm kiếm
@aws/nx-plugin - ts#api - Điền các tham số bắt buộc
- name: GameApi
- framework: trpc
- Nhấp
Generate
Bạn sẽ thấy một số tệp mới xuất hiện trong cây tệp của mình.
Kiểm tra chi tiết các tệp ts#api được tạo ra
Dưới đây là danh sách tất cả các tệp đã được tạo bởi generator ts#api. Chúng ta sẽ kiểm tra một số tệp quan trọng được đánh dấu trong cây tệp:
Thư mụcpackages/
Thư mụccommon/
Thư mụcconstructs/
Thư mụcsrc/
Thư mụcapp/ các cdk construct dành riêng cho ứng dụng
Thư mụcapis/
- game-api.ts cdk construct để tạo tRPC API của bạn
- index.ts
- …
- index.ts
Thư mụccore/ các cdk construct chung
Thư mụcapi/
- rest-api.ts cdk construct cơ sở cho API Gateway Rest API
- trpc-utils.ts tiện ích cho các CDK construct tRPC API
- utils.ts tiện ích cho các API construct
- index.ts
- runtime-config.ts
- index.ts
- project.json
- …
Thư mụcgame-api/ tRPC API
Thư mụcsrc/
Thư mụcclient/ vanilla client thường được dùng cho các cuộc gọi machine to machine bằng ts
- index.ts
Thư mụcmiddleware/ công cụ đo lường powertools
- error.ts
- index.ts
- logger.ts
- metrics.ts
- tracer.ts
Thư mụcschema/ định nghĩa đầu vào và đầu ra cho API của bạn
- index.ts
- echo.ts schema đầu vào và đầu ra mẫu
- z-async-iterable.ts wrapper Zod schema cho đầu ra subscription tRPC
Thư mụcprocedures/ các triển khai cụ thể cho các procedure/route API của bạn
- echo.ts triển khai procedure mẫu
- index.ts
- init.ts thiết lập context và middleware
- handler.ts điểm vào Lambda handler (sử dụng response streaming cho REST APIs)
- local-server.ts được dùng khi chạy tRPC server cục bộ
- router.ts định nghĩa tRPC router và tất cả các procedure
- project.json
- …
- vitest.workspace.ts
Hãy xem xét các tệp quan trọng này:
import { echo } from './procedures/echo.js';import { t } from './init.js';
export const router = t.router;
export const appRouter = router({ echo,});
export type AppRouter = typeof appRouter;Router định nghĩa tRPC router cho API của bạn và là nơi bạn sẽ khai báo tất cả các phương thức API. Như bạn có thể thấy ở trên, chúng ta có một phương thức gọi là echo với triển khai của nó trong tệp ./procedures/echo.ts. Điểm vào Lambda handler nằm trong handler.ts, được cấu hình tự động bởi generator.
import { publicProcedure } from '../init.js';import { EchoInputSchema, EchoOutputSchema,} from '../schema/echo.js';
export const echo = publicProcedure .input(EchoInputSchema) .output(EchoOutputSchema) .query((opts) => ({ message: opts.input.message }));Tệp này là triển khai của phương thức echo và như bạn có thể thấy được kiểm tra kiểu chặt chẽ bằng cách khai báo các cấu trúc dữ liệu đầu vào và đầu ra của nó.
import { z } from 'zod';
export const EchoInputSchema = z.object({ message: z.string().max(1024),});
export type IEchoInput = z.TypeOf<typeof EchoInputSchema>;
export const EchoOutputSchema = z.object({ message: z.string().max(1024),});
export type IEchoOutput = z.TypeOf<typeof EchoOutputSchema>;Tất cả các định nghĩa schema tRPC được định nghĩa bằng Zod và được xuất dưới dạng các kiểu typescript thông qua cú pháp z.TypeOf.
import { Construct } from 'constructs';import * as url from 'url';import { Distribution } from 'aws-cdk-lib/aws-cloudfront';import { Code, Runtime, Function, FunctionProps, Tracing,} from 'aws-cdk-lib/aws-lambda';import { RuntimeConfig } from '../../core/runtime-config.js';import { AuthorizationType, LambdaIntegration, ResponseTransferMode,} from 'aws-cdk-lib/aws-apigateway';import { Aspects, Duration } from 'aws-cdk-lib';import { PolicyDocument, PolicyStatement, Effect, AnyPrincipal, IGrantable, Grant,} from 'aws-cdk-lib/aws-iam';import { ApiIntegrations, IntegrationBuilder, RestApiIntegration,} from '../../core/api/utils.js';import { findCloudFrontDomainNames } from '../../core/cloudfront.js';import { AddCorsPreflightAspect, RestApi } from '../../core/api/rest-api.js';import { Procedures, routerToOperations } from '../../core/api/trpc-utils.js';import { AppRouter, appRouter } from '@dungeon-adventure/game-api';
// String union type for all API operation namestype Operations = Procedures<AppRouter>;
/** * Properties for creating a GameApi construct * * @template TIntegrations - Map of operation names to their integrations */export interface GameApiProps< TIntegrations extends ApiIntegrations<Operations, RestApiIntegration>,> { /** * Map of operation names to their API Gateway integrations */ integrations: TIntegrations; /** * Whether to enable AWS WAFv2 with the default managed ruleset on the API's default stage. * * @default true */ enableWaf?: boolean;}
/** * A CDK construct that creates and configures an AWS API Gateway REST API * specifically for GameApi. * @template TIntegrations - Map of operation names to their integrations */export class GameApi< TIntegrations extends ApiIntegrations<Operations, RestApiIntegration>,> extends RestApi<Operations, TIntegrations> { private allowedOrigins: readonly string[] = ['*'];
/** * Creates default integrations for all operations, which implement each operation as * its own individual lambda function. * * @param scope - The CDK construct scope * @returns An IntegrationBuilder with default lambda integrations */ public static defaultIntegrations = (scope: Construct) => { const rc = RuntimeConfig.ensure(scope); return IntegrationBuilder.rest({ pattern: 'isolated', operations: routerToOperations(appRouter), defaultIntegrationOptions: { runtime: Runtime.NODEJS_LATEST, handler: 'index.handler', code: Code.fromAsset( url.fileURLToPath( new URL( '../../../../../../dist/packages/game-api/bundle', import.meta.url, ), ), ), timeout: Duration.seconds(30), tracing: Tracing.ACTIVE, } as FunctionProps, buildDefaultIntegration: (op, props: FunctionProps) => { const handler = new Function(scope, `GameApi${op}Handler`, props); handler.addEnvironment('RUNTIME_CONFIG_APP_ID', rc.appConfigApplicationId); rc.grantReadAppConfig(handler); return { handler, integration: new LambdaIntegration(handler, { responseTransferMode: ResponseTransferMode.STREAM, }), }; }, }); };
constructor( scope: Construct, id: string, props: GameApiProps<TIntegrations>, ) { super(scope, id, { apiName: 'GameApi', defaultMethodOptions: { authorizationType: AuthorizationType.IAM, }, deployOptions: { tracingEnabled: true, }, policy: new PolicyDocument({ statements: [ // Open up OPTIONS to allow browsers to make unauthenticated preflight requests new PolicyStatement({ effect: Effect.ALLOW, principals: [new AnyPrincipal()], actions: ['execute-api:Invoke'], resources: ['execute-api:/*/OPTIONS/*'], }), ], }), operations: routerToOperations(appRouter), ...props, }); Aspects.of(this).add(new AddCorsPreflightAspect(() => this.allowedOrigins)); }
/** * Restricts CORS to the provided origins * * Configures the CloudFront distribution domains or origin strings * as the only permitted CORS origins in API Gateway preflight responses and the AWS * Lambda integrations. Any custom domain names (aliases) configured on a CloudFront * distribution are included automatically alongside its default `*.cloudfront.net` * domain. * * @param origins - The origin strings, CloudFront distributions, or objects containing a CloudFront distribution to grant CORS from */ public restrictCorsTo( ...origins: (string | Distribution | { cloudFrontDistribution: Distribution })[] ) { const allowedOrigins = origins.flatMap((origin) => typeof origin === 'string' ? [origin] : findCloudFrontDomainNames( 'cloudFrontDistribution' in origin ? origin.cloudFrontDistribution : origin, ).map((domain) => `https://${domain}`), );
this.allowedOrigins = allowedOrigins;
// Set ALLOWED_ORIGINS environment variable for all Lambda integrations Object.values(this.integrations).forEach((integration) => { if ('handler' in integration && integration.handler instanceof Function) { integration.handler.addEnvironment( 'ALLOWED_ORIGINS', allowedOrigins.join(','), ); } }); }
/** * Grants IAM permissions to invoke any method on this API. * * @param grantee - The IAM principal to grant permissions to */ public grantInvokeAccess(grantee: IGrantable) { // Here we grant grantee permission to call the api. // Machine to machine fine-grained access can be defined here using more specific principals (eg roles or // users) and resources (eg which api paths may be invoked by which principal) if required. this.api.addToResourcePolicy( new PolicyStatement({ effect: Effect.ALLOW, principals: [grantee.grantPrincipal], actions: ['execute-api:Invoke'], resources: ['execute-api:/*'], }), );
Grant.addToPrincipal({ grantee, actions: ['execute-api:Invoke'], resourceArns: [this.api.arnForExecuteApi('*', '/*', '*')], }); }}Đây là CDK construct định nghĩa GameApi của chúng ta. Nó cung cấp một phương thức defaultIntegrations tự động tạo một hàm Lambda cho mỗi procedure trong tRPC API của chúng ta, trỏ đến triển khai API đã được đóng gói. Điều này có nghĩa là tại thời điểm cdk synth, việc đóng gói không xảy ra (trái ngược với việc sử dụng NodeJsFunction) vì chúng ta đã đóng gói nó như một phần của mục tiêu build của dự án backend.
Tạo Story Agent
Phần tiêu đề “Tạo Story Agent”Bây giờ hãy tạo Story Agent của chúng ta.
Story agent: Dự án Python
Phần tiêu đề “Story agent: Dự án Python”Để tạo một dự án Python:
pnpm nx g @aws/nx-plugin:py#project --name=story --no-interactiveyarn nx g @aws/nx-plugin:py#project --name=story --no-interactivenpx nx g @aws/nx-plugin:py#project --name=story --no-interactivebunx nx g @aws/nx-plugin:py#project --name=story --no-interactiveBạn cũng có thể thực hiện chạy thử để xem những tệp nào sẽ bị thay đổi
pnpm nx g @aws/nx-plugin:py#project --name=story --no-interactive --dry-runyarn nx g @aws/nx-plugin:py#project --name=story --no-interactive --dry-runnpx nx g @aws/nx-plugin:py#project --name=story --no-interactive --dry-runbunx nx g @aws/nx-plugin:py#project --name=story --no-interactive --dry-run- Cài đặt Nx Console VSCode Plugin nếu bạn chưa cài đặt
- Mở Nx Console trong VSCode
- Nhấp
Generate (UI)trong phần "Common Nx Commands" - Tìm kiếm
@aws/nx-plugin - py#project - Điền các tham số bắt buộc
- name: story
- Nhấp
Generate
Bạn sẽ thấy một số tệp mới xuất hiện trong cây tệp của mình.
Kiểm tra chi tiết các tệp py#project được tạo ra
py#project tạo ra các tệp sau:
Thư mục.venv/ môi trường ảo duy nhất cho monorepo
- …
Thư mụcpackages/
Thư mụcstory/
Thư mụcdungeon_adventure_story/ module python
- …
Thư mụctests/
- …
- .python-version
- pyproject.toml
- project.json
- .python-version phiên bản python uv được ghim
- pyproject.toml
- uv.lock
Điều này đã cấu hình một dự án Python và UV Workspace với môi trường ảo dùng chung.
Story agent
Phần tiêu đề “Story agent”Để thêm một Strands agent vào dự án với generator py#agent:
pnpm nx g @aws/nx-plugin:py#agent --project=story --auth=cognito --protocol=ag-ui --no-interactiveyarn nx g @aws/nx-plugin:py#agent --project=story --auth=cognito --protocol=ag-ui --no-interactivenpx nx g @aws/nx-plugin:py#agent --project=story --auth=cognito --protocol=ag-ui --no-interactivebunx nx g @aws/nx-plugin:py#agent --project=story --auth=cognito --protocol=ag-ui --no-interactiveBạn cũng có thể thực hiện chạy thử để xem những tệp nào sẽ bị thay đổi
pnpm nx g @aws/nx-plugin:py#agent --project=story --auth=cognito --protocol=ag-ui --no-interactive --dry-runyarn nx g @aws/nx-plugin:py#agent --project=story --auth=cognito --protocol=ag-ui --no-interactive --dry-runnpx nx g @aws/nx-plugin:py#agent --project=story --auth=cognito --protocol=ag-ui --no-interactive --dry-runbunx nx g @aws/nx-plugin:py#agent --project=story --auth=cognito --protocol=ag-ui --no-interactive --dry-run- Cài đặt Nx Console VSCode Plugin nếu bạn chưa cài đặt
- Mở Nx Console trong VSCode
- Nhấp
Generate (UI)trong phần "Common Nx Commands" - Tìm kiếm
@aws/nx-plugin - py#agent - Điền các tham số bắt buộc
- project: story
- auth: cognito
- protocol: ag-ui
- Nhấp
Generate
Bạn sẽ thấy một số tệp mới xuất hiện trong cây tệp của mình.
Kiểm tra chi tiết các tệp py#agent được tạo ra
py#agent tạo ra các tệp sau:
Thư mụcpackages/
Thư mụcstory/
Thư mụcdungeon_adventure_story/ module python
Thư mụcagent/
- main.py điểm vào cho agent của bạn trong Bedrock AgentCore Runtime
- agent.py định nghĩa một agent và các tool mẫu
- session.py giải quyết SessionManager để duy trì trạng thái hội thoại
Thư mụcmiddleware/
- session_id_middleware.py liên kết session ID AgentCore đến cho yêu cầu
- Dockerfile định nghĩa docker image để triển khai lên AgentCore Runtime
Thư mụccommon/constructs/
Thư mụcsrc
Thư mụcapp/agents/story-agent/
- story-agent.ts construct để triển khai Story agent của bạn lên AgentCore Runtime
Hãy xem xét một số tệp chi tiết:
from contextlib import contextmanager
from strands import Agent, toolfrom strands.hooks import HookCallback, HookProviderfrom strands_tools import current_timefrom dungeon_adventure_agent_connection import log_model_errors, log_tool_errors
@tooldef subtract(a: int, b: int) -> int: return a - b
AGENT_HOOKS: list[HookProvider | HookCallback] = [log_model_errors, log_tool_errors]
@contextmanagerdef get_agent(): yield Agent( name="StoryAgent", description="StoryAgent Strands Agent", system_prompt="""You are a mathematical wizard.Use your tools for mathematical tasks.Refer to tools as your 'spellbook'.""", tools=[subtract, current_time], hooks=AGENT_HOOKS, )Điều này tạo ra một Strands agent mẫu và định nghĩa một công cụ trừ. log_model_errors và log_tool_errors là các hook từ dự án dungeon_adventure_agent_connection dùng chung ghi lại các lỗi model/tool thay vì để chúng thất bại im lặng.
import uuid
from fastapi import Requestfrom starlette.middleware.base import BaseHTTPMiddleware
from dungeon_adventure_agent_connection import session_id_context
SESSION_ID_HEADER = "x-amzn-bedrock-agentcore-runtime-session-id"
class SessionIdMiddleware(BaseHTTPMiddleware): """Bind the session ID for this request so downstream MCP / A2A clients forward it on outbound calls."""
async def dispatch(self, request: Request, call_next): session_id = request.headers.get(SESSION_ID_HEADER) or str(uuid.uuid4()) with session_id_context(session_id): return await call_next(request)SessionIdMiddleware liên kết session ID runtime AgentCore đến vào một ContextVar trong suốt thời gian của yêu cầu.
import loggingimport uuidfrom contextlib import asynccontextmanager
from ag_ui.core import EventType, RunAgentInput, RunErrorEventfrom ag_ui.encoder import EventEncoderfrom ag_ui_strands import StrandsAgent, StrandsAgentConfigfrom dungeon_adventure_agent_connection import get_current_session_id, session_id_contextfrom fastapi import FastAPI, Requestfrom fastapi.middleware.cors import CORSMiddlewarefrom fastapi.responses import StreamingResponse
from .agent import AGENT_HOOKS, get_agentfrom .middleware.session_id_middleware import SESSION_ID_HEADER, SessionIdMiddlewarefrom .session import get_session_manager
logging.basicConfig(level=logging.INFO)
@asynccontextmanagerasync def lifespan(app: FastAPI): with get_agent() as agent: app.state.agui_agent = StrandsAgent( agent=agent, name="StoryAgent", description="A Strands Agent exposed via the AG-UI protocol.", # A per-thread session manager, not the template Agent's own, since # AG-UI caches one Strands agent per thread_id. config=StrandsAgentConfig(session_manager_provider=lambda _input_data: get_session_manager()), # Required as well as on the template Agent: AG-UI keeps only the # built HookRegistry, so hooks it can't read back are never # registered and model/tool failures go unreported. hooks=AGENT_HOOKS, ) yield
app = FastAPI(title="AWS Strands - StoryAgent", lifespan=lifespan)app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"],)app.add_middleware(SessionIdMiddleware)
@app.post("/invocations")async def invocations(request: Request): # Validate the body manually since AgentCore may omit Content-Type. encoder = EventEncoder(accept=request.headers.get("accept") or "") raw = await request.body() try: input_data = RunAgentInput.model_validate_json(raw) except Exception as exc: message = f"Invalid RunAgentInput: {str(exc)[:200]}"
async def _bad(): yield encoder.encode(RunErrorEvent(type=EventType.RUN_ERROR, message=message, code="BAD_REQUEST"))
return StreamingResponse(_bad(), media_type=encoder.get_content_type())
session_id = request.headers.get(SESSION_ID_HEADER) or get_current_session_id()
async def event_generator(): # Re-bind the session: the streaming body runs outside the middleware. with session_id_context(session_id or str(uuid.uuid4())): async for event in request.app.state.agui_agent.run(input_data): try: yield encoder.encode(event) except Exception as e: error_event = RunErrorEvent( type=EventType.RUN_ERROR, message=f"Encoding error: {e}", code="ENCODING_ERROR", ) yield encoder.encode(error_event) break
return StreamingResponse(event_generator(), media_type=encoder.get_content_type())
@app.get("/ping")async def ping(): return {"status": "healthy"}Đây là điểm vào cho agent. Vì chúng ta đã chọn --protocol=ag-ui, generator bọc Strands Agent của chúng ta với StrandsAgent từ ag_ui_strands và gắn nó vào một ứng dụng FastAPI sử dụng giao thức AG-UI — đây là thứ CopilotKit sẽ nói chuyện từ website React. Agent được xây dựng bên trong một handler lifespan — không phải tại thời điểm import — vì vậy việc khởi động container, không phải import module, sở hữu việc xây dựng, và mỗi phiên AgentCore nhận được container riêng của nó. SessionIdMiddleware mà chúng ta đã thấy ở trên chuyển tiếp session ID runtime AgentCore đến để bất kỳ client MCP/A2A nào chúng ta kết nối sau này (ví dụ: Inventory MCP server trong Module 2) tự động chuyển tiếp nó trên các cuộc gọi đi. Vì AG-UI lưu trữ một Strands agent cho mỗi thread_id, chúng ta cắm vào một session_manager_provider thay vì session manager của template agent — điều này cung cấp cho mỗi thread SessionManager riêng của nó để lịch sử hội thoại được duy trì qua các lượt. Hàm get_session_manager() đó đến từ một session.py được tạo ra: khi triển khai, nó trả về một strands.session.S3SessionManager được hỗ trợ bởi một S3 bucket mà generator cung cấp tự động; trong agent-dev (LOCAL_DEV=true) nó luôn trả về một FileSessionManager ghi vào một thư mục tạm cục bộ thay thế, bất kể cấu hình triển khai.
import { Fn, Lazy, Names, RemovalPolicy, Stack } from 'aws-cdk-lib';import { Platform } from 'aws-cdk-lib/aws-ecr-assets';import { Connections, IConnectable } from 'aws-cdk-lib/aws-ec2';import { BlockPublicAccess, Bucket, BucketEncryption,} from 'aws-cdk-lib/aws-s3';import { Key } from 'aws-cdk-lib/aws-kms';import { CfnDelivery, CfnDeliveryDestination, CfnDeliverySource, LogGroup, RetentionDays,} from 'aws-cdk-lib/aws-logs';import { Construct } from 'constructs';import * as path from 'path';import * as url from 'url';import { AgentRuntimeArtifact, ProtocolType, Runtime, RuntimeProps, RuntimeAuthorizerConfiguration,} from 'aws-cdk-lib/aws-bedrockagentcore';import { PolicyStatement, Effect, ServicePrincipal, IGrantable, IPrincipal,} from 'aws-cdk-lib/aws-iam';import { IUserPool, IUserPoolClient } from 'aws-cdk-lib/aws-cognito';import { suppressRules } from '../../../core/checkov.js';import { RuntimeConfig } from '../../../core/runtime-config.js';import { findWorkspaceRoot } from '../../../core/workspace.js';
export type StoryAgentProps = Omit< RuntimeProps, | 'runtimeName' | 'protocolConfiguration' | 'agentRuntimeArtifact' | 'authorizerConfiguration'> & { /** * Identity details for Cognito Authentication */ identity: { userPool: IUserPool; userPoolClient: IUserPoolClient; }; /** * Removal policy for the session bucket holding the agent's conversation * history. Defaults to retaining it so a stack `destroy` doesn't silently * delete session data — set to `RemovalPolicy.DESTROY` for sandbox/CI teardown. * * @default RemovalPolicy.RETAIN */ readonly sessionBucketRemovalPolicy?: RemovalPolicy;};
export class StoryAgent extends Construct implements IGrantable, IConnectable { public readonly dockerImage: AgentRuntimeArtifact; public readonly agentCoreRuntime: Runtime; /** Default Gateway target name for this agent. */ public readonly agentName = 'story-agent'; /** Inbound auth — a fronting Gateway uses this to pick its outbound credential. */ public readonly auth = 'cognito';
constructor(scope: Construct, id: string, props: StoryAgentProps) { super(scope, id);
const rc = RuntimeConfig.ensure(this);
// Resolve the bundle output directory containing the Dockerfile and built artifacts const bundleDir = path.join( findWorkspaceRoot(url.fileURLToPath(new URL(import.meta.url))), 'dist/packages/story/docker/story-agent', );
this.dockerImage = AgentRuntimeArtifact.fromAsset(bundleDir, { platform: Platform.LINUX_ARM64, });
const { identity, sessionBucketRemovalPolicy = RemovalPolicy.RETAIN, ...restProps } = props ?? {};
const sessionKey = new Key(this, 'SessionKey', { enableKeyRotation: true, });
// Allow CloudWatch Logs to use the session key for server access log delivery. const stack = Stack.of(this); sessionKey.addToResourcePolicy( new PolicyStatement({ effect: Effect.ALLOW, principals: [ new ServicePrincipal(`logs.${stack.region}.amazonaws.com`), ], actions: [ 'kms:Encrypt', 'kms:Decrypt', 'kms:ReEncrypt*', 'kms:GenerateDataKey*', 'kms:DescribeKey', ], resources: ['*'], conditions: { ArnLike: { 'kms:EncryptionContext:aws:logs:arn': `arn:aws:logs:${stack.region}:${stack.account}:log-group:*`, }, }, }), );
const sessionAccessLogs = new LogGroup(this, 'SessionAccessLogs', { retention: RetentionDays.ONE_YEAR, encryptionKey: sessionKey, removalPolicy: RemovalPolicy.DESTROY, });
const sessionBucket = new Bucket(this, 'SessionBucket', { enforceSSL: true, removalPolicy: sessionBucketRemovalPolicy, encryption: BucketEncryption.KMS, encryptionKey: sessionKey, blockPublicAccess: BlockPublicAccess.BLOCK_ALL, }); suppressRules( sessionBucket, ['CKV_AWS_21'], 'Session data does not need versioning enabled', ); suppressRules( sessionBucket, ['CKV2_AWS_61'], 'Lifecycle configuration not required for session data', ); suppressRules( sessionBucket, ['CKV_AWS_144'], 'Cross-region replication not required for session data', ); suppressRules( sessionBucket, ['CKV2_AWS_62'], 'Event notifications not required for session data', ); suppressRules( sessionBucket, ['CKV_AWS_18'], 'Server access logs are delivered to CloudWatch Logs', );
const sessionAccessLogsSource: CfnDeliverySource = new CfnDeliverySource( this, 'SessionAccessLogsSource', { name: Lazy.string({ produce: () => Names.uniqueResourceName(sessionAccessLogsSource, { maxLength: 60, }), }), logType: 'S3_SERVER_ACCESS_LOGS', resourceArn: sessionBucket.bucketArn, }, ); const sessionBucketPolicy = sessionBucket.policy; if (sessionBucketPolicy) { sessionAccessLogsSource.node.addDependency(sessionBucketPolicy); } const sessionAccessLogsDestination: CfnDeliveryDestination = new CfnDeliveryDestination(this, 'SessionAccessLogsDestination', { name: Lazy.string({ produce: () => Names.uniqueResourceName(sessionAccessLogsDestination, { maxLength: 60, }), }), destinationResourceArn: sessionAccessLogs.logGroupArn, }); const sessionAccessLogsDelivery = new CfnDelivery( this, 'SessionAccessLogsDelivery', { deliverySourceName: sessionAccessLogsSource.name, deliveryDestinationArn: sessionAccessLogsDestination.attrArn, }, ); sessionAccessLogsDelivery.addDependency(sessionAccessLogsSource);
this.agentCoreRuntime = new Runtime(this, 'StoryAgent', { runtimeName: Lazy.string({ produce: () => Names.uniqueResourceName(this.agentCoreRuntime, { maxLength: 40 }), }), protocolConfiguration: ProtocolType.HTTP, agentRuntimeArtifact: this.dockerImage, authorizerConfiguration: RuntimeAuthorizerConfiguration.usingCognito( identity.userPool, [identity.userPoolClient], ), // Receive the caller's Authorization header (validated by the authorizer). requestHeaderConfiguration: { allowlistedHeaders: ['Authorization'], }, ...restProps, environmentVariables: { RUNTIME_CONFIG_APP_ID: rc.appConfigApplicationId, ...restProps?.environmentVariables, }, });
// Grant access for the agent to invoke bedrock models this.agentCoreRuntime.addToRolePolicy( new PolicyStatement({ actions: [ 'bedrock:InvokeModel', 'bedrock:InvokeModelWithResponseStream', ], resources: [ 'arn:aws:bedrock:*:*:foundation-model/*', 'arn:aws:bedrock:*:*:inference-profile/*', ], }), );
sessionBucket.grantReadWrite(this.agentCoreRuntime);
rc.grantReadAppConfig(this.agentCoreRuntime);
rc.set('agentcore', 'agentRuntimes', { ...rc.get('agentcore').agentRuntimes, StoryAgent: { arn: this.agentCoreRuntime.agentRuntimeArn, session: { bucketName: sessionBucket.bucketName, }, }, });
rc.set('connection', 'agentRuntimes', { ...rc.get('connection').agentRuntimes, StoryAgent: this.agentCoreRuntime.agentRuntimeArn, }); }
/** * The principal to grant permissions to. */ public get grantPrincipal(): IPrincipal { return this.agentCoreRuntime.grantPrincipal; }
/** * Network connections for this agent runtime. */ public get connections(): Connections { return this.agentCoreRuntime.connections; }
/** * The HTTPS invocation URL of the runtime. */ public get invocationUrl(): string { // The URL must URL-encode the runtime ARN (':' -> '%3A', '/' -> '%2F'). // The ARN is a CDK token, so encode at deploy time via Fn.join/Fn.split. const encodedArn = Fn.join( '%2F', Fn.split( '/', Fn.join('%3A', Fn.split(':', this.agentCoreRuntime.agentRuntimeArn)), ), ); return `https://bedrock-agentcore.${Stack.of(this).region}.amazonaws.com/runtimes/${encodedArn}/invocations?qualifier=DEFAULT`; }}Điều này cấu hình một CDK AgentRuntimeArtifact tải lên Docker image của agent lên ECR và lưu trữ nó bằng AgentCore Runtime. Vì chúng ta đã chọn --auth=cognito, construct yêu cầu danh tính user pool/client và ủy quyền các lời gọi AgentCore Runtime thông qua Cognito, chuyển tiếp header Authorization của người gọi. Nó cũng cung cấp session bucket mà session.py của Story Agent đọc lại tại runtime — một S3 bucket được mã hóa KMS với server access logs được gửi đến CloudWatch Logs — cấp cho agent quyền đọc/ghi vào nó, cấp cho nó quyền gọi các model Bedrock, và đăng ký ARN và tên bucket của nó trong RuntimeConfig để cả agent (tại runtime, thông qua AppConfig) và Game API (tại thời điểm synth, thông qua invocationUrl) đều có thể tìm thấy nó.
Bạn có thể nhận thấy một Dockerfile bổ sung, tham chiếu Docker image từ dự án story, cho phép chúng ta đặt Dockerfile và mã nguồn agent cùng vị trí.
Thiết lập các công cụ Inventory
Phần tiêu đề “Thiết lập các công cụ Inventory”Inventory: Dự án TypeScript
Phần tiêu đề “Inventory: Dự án TypeScript”Hãy tạo một MCP server để cung cấp các công cụ cho Story Agent của chúng ta quản lý kho đồ của người chơi.
Đầu tiên, chúng ta tạo một dự án TypeScript:
pnpm nx g @aws/nx-plugin:ts#project --name=inventory --no-interactiveyarn nx g @aws/nx-plugin:ts#project --name=inventory --no-interactivenpx nx g @aws/nx-plugin:ts#project --name=inventory --no-interactivebunx nx g @aws/nx-plugin:ts#project --name=inventory --no-interactiveBạn cũng có thể thực hiện chạy thử để xem những tệp nào sẽ bị thay đổi
pnpm nx g @aws/nx-plugin:ts#project --name=inventory --no-interactive --dry-runyarn nx g @aws/nx-plugin:ts#project --name=inventory --no-interactive --dry-runnpx nx g @aws/nx-plugin:ts#project --name=inventory --no-interactive --dry-runbunx nx g @aws/nx-plugin:ts#project --name=inventory --no-interactive --dry-run- Cài đặt Nx Console VSCode Plugin nếu bạn chưa cài đặt
- Mở Nx Console trong VSCode
- Nhấp
Generate (UI)trong phần "Common Nx Commands" - Tìm kiếm
@aws/nx-plugin - ts#project - Điền các tham số bắt buộc
- name: inventory
- Nhấp
Generate
Điều này sẽ tạo ra một dự án TypeScript trống.
Kiểm tra chi tiết các tệp ts#project được tạo ra
Generator ts#project tạo ra các tệp sau.
Thư mụcpackages/
Thư mụcinventory/
Thư mụcsrc/
- index.ts điểm vào với hàm ví dụ
- project.json cấu hình dự án
- vitest.config.mts cấu hình kiểm thử
- tsconfig.json cấu hình typescript cơ sở cho dự án
- tsconfig.lib.json cấu hình typescript cho dự án nhắm đến biên dịch và đóng gói
- tsconfig.spec.json cấu hình typescript cho các bài kiểm thử
- tsconfig.base.json được cập nhật để cấu hình alias cho các dự án khác tham chiếu đến đây
Inventory: MCP server
Phần tiêu đề “Inventory: MCP server”Tiếp theo, chúng ta sẽ thêm một MCP server vào dự án TypeScript của mình:
pnpm nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactiveyarn nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactivenpx nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactivebunx nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactiveBạn cũng có thể thực hiện chạy thử để xem những tệp nào sẽ bị thay đổi
pnpm nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactive --dry-runyarn nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactive --dry-runnpx nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactive --dry-runbunx nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactive --dry-run- Cài đặt Nx Console VSCode Plugin nếu bạn chưa cài đặt
- Mở Nx Console trong VSCode
- Nhấp
Generate (UI)trong phần "Common Nx Commands" - Tìm kiếm
@aws/nx-plugin - ts#mcp-server - Điền các tham số bắt buộc
- project: inventory
- Nhấp
Generate
Điều này sẽ thêm một MCP server.
Kiểm tra chi tiết các tệp ts#mcp-server được tạo ra
Generator ts#mcp-server tạo ra các tệp sau.
Thư mụcpackages/
Thư mụcinventory/
Thư mụcsrc/mcp-server/
- index.ts barrel export
- server.ts tạo MCP server
Thư mụctools/
- divide.ts công cụ ví dụ
Thư mụcresources/
- sample-guidance.ts tài nguyên ví dụ
- stdio.ts điểm vào cho MCP với STDIO transport
- http.ts điểm vào cho MCP với Streamable HTTP transport
- Dockerfile xây dựng image cho AgentCore Runtime
- rolldown.config.ts cấu hình để đóng gói MCP server để triển khai lên AgentCore
Thư mụccommon/constructs/
Thư mụcsrc
Thư mụcapp/mcp-servers/inventory-mcp-server/
- inventory-mcp-server.ts construct để triển khai inventory MCP server của bạn lên AgentCore Runtime
Tạo cơ sở dữ liệu trò chơi
Phần tiêu đề “Tạo cơ sở dữ liệu trò chơi”Trạng thái trò chơi của chúng ta — các trò chơi đã lưu và kho đồ của mỗi người chơi — nằm trong Amazon DynamoDB. Tạo một dự án DynamoDB có tên DungeonDb với generator ts#dynamodb:
pnpm nx g @aws/nx-plugin:ts#dynamodb --name=DungeonDb --no-interactiveyarn nx g @aws/nx-plugin:ts#dynamodb --name=DungeonDb --no-interactivenpx nx g @aws/nx-plugin:ts#dynamodb --name=DungeonDb --no-interactivebunx nx g @aws/nx-plugin:ts#dynamodb --name=DungeonDb --no-interactiveBạn cũng có thể thực hiện chạy thử để xem những tệp nào sẽ bị thay đổi
pnpm nx g @aws/nx-plugin:ts#dynamodb --name=DungeonDb --no-interactive --dry-runyarn nx g @aws/nx-plugin:ts#dynamodb --name=DungeonDb --no-interactive --dry-runnpx nx g @aws/nx-plugin:ts#dynamodb --name=DungeonDb --no-interactive --dry-runbunx nx g @aws/nx-plugin:ts#dynamodb --name=DungeonDb --no-interactive --dry-run- Cài đặt Nx Console VSCode Plugin nếu bạn chưa cài đặt
- Mở Nx Console trong VSCode
- Nhấp
Generate (UI)trong phần "Common Nx Commands" - Tìm kiếm
@aws/nx-plugin - ts#dynamodb - Điền các tham số bắt buộc
- name: DungeonDb
- Nhấp
Generate
Bạn sẽ thấy một số tệp mới xuất hiện trong cây tệp của mình.
Kiểm tra chi tiết các tệp ts#dynamodb được tạo ra
Generator ts#dynamodb tạo ra các tệp sau.
Thư mụcpackages/
Thư mụcdungeon-db/
- config.json Cấu hình DynamoDB bao gồm cổng, tên bảng, cài đặt container và Global Secondary Indexes
Thư mụcsrc/
- index.ts điểm vào và xuất
- client.ts DynamoDB client singleton và giải quyết tên bảng
Thư mụcentities/
- example.ts entity ElectroDB ví dụ (chúng ta sẽ thay thế cái này)
- index.ts xuất entity
- project.json thêm các mục tiêu
devvàpull-image
Thư mụccommon/
Thư mụcscripts/
Thư mụcsrc/
Thư mụcdynamodb/
- create-local-table.ts tạo bảng trong DynamoDB Local
- pull-image.ts kéo image DynamoDB Local
- start-container.ts khởi động container DynamoDB Local
Thư mụcconstructs/
Thư mụcsrc/
Thư mụcapp/dynamodb/
- dungeon-db.ts construct để cung cấp bảng của bạn
Thư mụccore/
- dynamodb.ts construct bảng DynamoDB chung
src/client.ts được tạo ra xuất getDynamoDBClient() và resolveTableName(). Khi LOCAL_DEV=true (được đặt tự động bởi các mục tiêu dev) chúng kết nối đến DynamoDB Local; nếu không chúng kết nối đến AWS và giải quyết tên bảng đã triển khai từ Runtime Configuration. Chúng ta sẽ mô hình hóa các entity Game và Inventory của mình trong dự án này trong Module 2.
Để biết thêm chi tiết, hãy tham khảo hướng dẫn ts#dynamodb generator.
Tạo Giao diện Người dùng (UI)
Phần tiêu đề “Tạo Giao diện Người dùng (UI)”Tiếp theo, chúng ta sẽ tạo UI cho phép bạn tương tác với trò chơi.
Game UI: Website
Phần tiêu đề “Game UI: Website”Để tạo UI, tạo một website có tên GameUI bằng các bước sau:
pnpm nx g @aws/nx-plugin:ts#website --name=GameUI --ux=shadcn --no-interactiveyarn nx g @aws/nx-plugin:ts#website --name=GameUI --ux=shadcn --no-interactivenpx nx g @aws/nx-plugin:ts#website --name=GameUI --ux=shadcn --no-interactivebunx nx g @aws/nx-plugin:ts#website --name=GameUI --ux=shadcn --no-interactiveBạn cũng có thể thực hiện chạy thử để xem những tệp nào sẽ bị thay đổi
pnpm nx g @aws/nx-plugin:ts#website --name=GameUI --ux=shadcn --no-interactive --dry-runyarn nx g @aws/nx-plugin:ts#website --name=GameUI --ux=shadcn --no-interactive --dry-runnpx nx g @aws/nx-plugin:ts#website --name=GameUI --ux=shadcn --no-interactive --dry-runbunx nx g @aws/nx-plugin:ts#website --name=GameUI --ux=shadcn --no-interactive --dry-run- Cài đặt Nx Console VSCode Plugin nếu bạn chưa cài đặt
- Mở Nx Console trong VSCode
- Nhấp
Generate (UI)trong phần "Common Nx Commands" - Tìm kiếm
@aws/nx-plugin - ts#website - Điền các tham số bắt buộc
- name: GameUI
- ux: shadcn
- Nhấp
Generate
Bạn sẽ thấy một số tệp mới xuất hiện trong cây tệp của mình.
Kiểm tra chi tiết các tệp ts#website được tạo ra
ts#website tạo ra các tệp sau. Hãy xem xét một số tệp quan trọng được đánh dấu trong cây tệp:
Thư mụcpackages/
Thư mụccommon/
Thư mụcconstructs/
Thư mụcsrc/
Thư mụcapp/ các cdk construct dành riêng cho ứng dụng
Thư mụcstatic-websites/
- game-ui.ts cdk construct để tạo Game UI của bạn
Thư mụccore/
- static-website.ts construct website tĩnh chung
Thư mụcgame-ui/
Thư mụcpublic/
- …
Thư mụcsrc/
Thư mụccomponents/
Thư mụcAppLayout/
- index.tsx bố cục trang tổng thể sử dụng shadcn
SidebarProvider+ header
- index.tsx bố cục trang tổng thể sử dụng shadcn
- app-sidebar.tsx thanh bên shadcn mặc định với các mục điều hướng
- alert.tsx, spinner.tsx các nguyên thủy phản hồi được bọc bởi shadcn
Thư mụcroutes/ các route dựa trên tệp @tanstack/react-router
- index.tsx trang root ’/’
- __root.tsx tất cả các trang sử dụng thành phần này làm cơ sở
- config.ts
- main.tsx điểm vào React
- routeTree.gen.ts được tự động cập nhật bởi @tanstack/react-router
- styles.css nhập các biến toàn cục shadcn dùng chung (Tailwind v4)
- index.html
- project.json
- vite.config.mts
- …
Thư mụccommon/
Thư mụcshadcn/ thư viện shadcn/ui dùng chung (token chủ đề,
Button,Card,Input,Sidebar, …) được nhập bởi mọi websiteux=shadcn- src/components/ui/*
- src/styles/globals.css Tailwind + token thiết kế shadcn
- …
import * as url from 'url';import { Construct } from 'constructs';import { StaticWebsite, StaticWebsiteProps } from '../../core/index.js';
export type GameUIProps = Omit< StaticWebsiteProps, 'websiteName' | 'websiteFilePath'>;
export class GameUI extends StaticWebsite { constructor(scope: Construct, id: string, props?: GameUIProps) { super(scope, id, { ...props, websiteName: 'GameUI', websiteFilePath: url.fileURLToPath( new URL( '../../../../../../dist/packages/game-ui/bundle', import.meta.url, ), ), }); }}Đây là CDK construct định nghĩa GameUI của chúng ta. Nó đã cấu hình đường dẫn tệp đến bundle được tạo ra cho UI dựa trên Vite của chúng ta. Điều này có nghĩa là tại thời điểm build, việc đóng gói xảy ra trong mục tiêu build của dự án game-ui và đầu ra được sử dụng ở đây.
import React from 'react';import { createRoot } from 'react-dom/client';import { RouterProvider, createRouter } from '@tanstack/react-router';import { routeTree } from './routeTree.gen';import './styles.css';
export type RouterProviderContext = {};
const router = createRouter({ routeTree, context: {} });
declare module '@tanstack/react-router' { interface Register { router: typeof router; }}
const App = () => <RouterProvider router={router} context={{}} />;
const root = document.getElementById('root');root && createRoot(root).render( <React.StrictMode> <App /> </React.StrictMode>, );Đây là điểm vào nơi React được gắn kết. Kiểu dáng đến từ các token Tailwind v4 được nhập thông qua styles.css. @tanstack/react-router được cấu hình trong chế độ định tuyến dựa trên tệp: miễn là dev server đang chạy, bất kỳ tệp nào bạn tạo trong routes/ đều được nhận tự động và cây route được tái tạo. Các generator sau này (auth, connection) sẽ vá AST tệp này để bọc <App /> trong các provider bổ sung.
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/')({ component: RouteComponent,});
function RouteComponent() { return ( <div className="text-center"> <header> <h1>Welcome</h1> <p>Welcome to your new React website!</p> </header> </div> );}Một thành phần sẽ được hiển thị khi điều hướng đến route /. @tanstack/react-router sẽ quản lý Route cho bạn bất cứ khi nào bạn tạo/di chuyển tệp này (miễn là dev server đang chạy).
Game UI: Auth
Phần tiêu đề “Game UI: Auth”Hãy cấu hình Game UI của chúng ta để yêu cầu truy cập được xác thực thông qua Amazon Cognito bằng các bước sau:
pnpm nx g @aws/nx-plugin:ts#website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactiveyarn nx g @aws/nx-plugin:ts#website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactivenpx nx g @aws/nx-plugin:ts#website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactivebunx nx g @aws/nx-plugin:ts#website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactiveBạn cũng có thể thực hiện chạy thử để xem những tệp nào sẽ bị thay đổi
pnpm nx g @aws/nx-plugin:ts#website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-runyarn nx g @aws/nx-plugin:ts#website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-runnpx nx g @aws/nx-plugin:ts#website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-runbunx nx g @aws/nx-plugin:ts#website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run- Cài đặt Nx Console VSCode Plugin nếu bạn chưa cài đặt
- Mở Nx Console trong VSCode
- Nhấp
Generate (UI)trong phần "Common Nx Commands" - Tìm kiếm
@aws/nx-plugin - ts#website#auth - Điền các tham số bắt buộc
- cognitoDomain: game-ui
- project: @dungeon-adventure/game-ui
- allowSignup: true
- Nhấp
Generate
Bạn sẽ thấy một số tệp mới xuất hiện/thay đổi trong cây tệp của mình.
Kiểm tra chi tiết các tệp ts#website#auth được tạo ra
Generator ts#website#auth cập nhật/tạo ra các tệp sau. Hãy xem xét một số tệp quan trọng được đánh dấu trong cây tệp:
Thư mụcpackages/
Thư mụccommon/
Thư mụcconstructs/
Thư mụcsrc/
Thư mụccore/
- user-identity.ts cdk construct để tạo user/identity pools
Thư mụcgame-ui/
Thư mụcsrc/
Thư mụccomponents/
Thư mụcAppLayout/
- index.tsx thêm người dùng đã đăng nhập/đăng xuất vào header
Thư mụcCognitoAuth/
- index.tsx quản lý đăng nhập vào Cognito
Thư mụcRuntimeConfig/
- index.tsx lấy
runtime-config.jsonvà cung cấp cho các con thông qua context
- index.tsx lấy
Thư mụchooks/
- useRuntimeConfig.tsx
- main.tsx Được cập nhật để thêm Cognito
import { useAuth } from 'react-oidc-context';import CognitoAuth from './components/CognitoAuth';import { useRuntimeConfig } from './hooks/useRuntimeConfig';import RuntimeConfigProvider from './components/RuntimeConfig';import React from 'react';import { createRoot } from 'react-dom/client';import { RouterProvider, createRouter } from '@tanstack/react-router';import { routeTree } from './routeTree.gen';import './styles.css';export type RouterProviderContext = {};export type RouterProviderContext = { runtimeConfig?: ReturnType<typeof useRuntimeConfig>; auth?: ReturnType<typeof useAuth>;};const router = createRouter({ routeTree, context: {} });const router = createRouter({ routeTree, context: { runtimeConfig: undefined, auth: undefined },});// Register the router instance for type safetydeclare module '@tanstack/react-router' { interface Register { router: typeof router; }}const App = () => <RouterProvider router={router} context={{}} />;const App = () => { const auth = useAuth(); const runtimeConfig = useRuntimeConfig(); return <RouterProvider router={router} context={{ runtimeConfig, auth }} />;};const root = document.getElementById('root');root && createRoot(root).render( <React.StrictMode> <RuntimeConfigProvider> <CognitoAuth> <App /> </CognitoAuth> </RuntimeConfigProvider> </React.StrictMode>, );Các thành phần RuntimeConfigProvider và CognitoAuth đã được thêm vào tệp main.tsx thông qua một biến đổi AST. Điều này cho phép thành phần CognitoAuth xác thực với Amazon Cognito bằng cách lấy runtime-config.json chứa cấu hình kết nối cognito cần thiết để thực hiện các cuộc gọi backend đến đúng đích.
Game UI: Kết nối với Game API
Phần tiêu đề “Game UI: Kết nối với Game API”Hãy cấu hình Game UI của chúng ta để kết nối với Game API đã tạo trước đó.
pnpm nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactiveyarn nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactivenpx nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactivebunx nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactiveBạn cũng có thể thực hiện chạy thử để xem những tệp nào sẽ bị thay đổi
pnpm nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-runyarn nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-runnpx nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-runbunx nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run- Cài đặt Nx Console VSCode Plugin nếu bạn chưa cài đặt
- Mở Nx Console trong VSCode
- Nhấp
Generate (UI)trong phần "Common Nx Commands" - Tìm kiếm
@aws/nx-plugin - connection - Điền các tham số bắt buộc
- sourceProject: @dungeon-adventure/game-ui
- targetProject: @dungeon-adventure/game-api
- Nhấp
Generate
Bạn sẽ thấy một số tệp mới xuất hiện/thay đổi trong cây tệp của mình.
Kiểm tra các tệp kết nối UI → tRPC
Generator connection tạo ra/cập nhật các tệp sau. Hãy xem xét một số tệp quan trọng được đánh dấu trong cây tệp:
Thư mụcpackages/
Thư mụcgame-ui/
Thư mụcsrc/
Thư mụccomponents/
- GameApiClientProvider.tsx thiết lập client GameAPI
Thư mụchooks/
- useGameApi.tsx hooks để gọi GameApi
- main.tsx tiêm các provider client trpc
- package.json
import { useContext } from 'react';import { GameApiTRPCContext } from '../components/GameApiClientProvider';
export const useGameApi = () => { const container = useContext(GameApiTRPCContext); if (!container) { throw new Error('useGameApi must be used within GameApiClientProvider'); } return container.optionsProxy;};
export const useGameApiClient = () => { const container = useContext(GameApiTRPCContext); if (!container) { throw new Error( 'useGameApiClient must be used within GameApiClientProvider', ); } return container.client;};Hook này cung cấp quyền truy cập vào tRPC client để gọi GameApi. Để biết ví dụ về cách gọi tRPC APIs, hãy tham khảo hướng dẫn sử dụng tRPC hook.
import GameApiClientProvider from './components/GameApiClientProvider';import QueryClientProvider from './components/QueryClientProvider';import { useAuth } from 'react-oidc-context';import CognitoAuth from './components/CognitoAuth';import { useRuntimeConfig } from './hooks/useRuntimeConfig';import RuntimeConfigProvider from './components/RuntimeConfig';import React from 'react';import { createRoot } from 'react-dom/client';import { RouterProvider, createRouter } from '@tanstack/react-router';import { routeTree } from './routeTree.gen';import './styles.css';...const root = document.getElementById('root');root && createRoot(root).render( <React.StrictMode> <RuntimeConfigProvider> <CognitoAuth> <QueryClientProvider> <GameApiClientProvider> <App /> </GameApiClientProvider> </QueryClientProvider> </CognitoAuth> </RuntimeConfigProvider> </React.StrictMode>, );Tệp main.tsx đã được cập nhật thông qua một biến đổi AST để tiêm các provider tRPC.
Story Agent: Kết nối với Inventory MCP Server
Phần tiêu đề “Story Agent: Kết nối với Inventory MCP Server”Hãy kết nối Story Agent của chúng ta với Inventory MCP server để agent có thể khám phá và gọi các công cụ của MCP server.
pnpm nx g @aws/nx-plugin:connection --sourceProject=story --targetProject=inventory --no-interactiveyarn nx g @aws/nx-plugin:connection --sourceProject=story --targetProject=inventory --no-interactivenpx nx g @aws/nx-plugin:connection --sourceProject=story --targetProject=inventory --no-interactivebunx nx g @aws/nx-plugin:connection --sourceProject=story --targetProject=inventory --no-interactiveBạn cũng có thể thực hiện chạy thử để xem những tệp nào sẽ bị thay đổi
pnpm nx g @aws/nx-plugin:connection --sourceProject=story --targetProject=inventory --no-interactive --dry-runyarn nx g @aws/nx-plugin:connection --sourceProject=story --targetProject=inventory --no-interactive --dry-runnpx nx g @aws/nx-plugin:connection --sourceProject=story --targetProject=inventory --no-interactive --dry-runbunx nx g @aws/nx-plugin:connection --sourceProject=story --targetProject=inventory --no-interactive --dry-run- Cài đặt Nx Console VSCode Plugin nếu bạn chưa cài đặt
- Mở Nx Console trong VSCode
- Nhấp
Generate (UI)trong phần "Common Nx Commands" - Tìm kiếm
@aws/nx-plugin - connection - Điền các tham số bắt buộc
- sourceProject: story
- targetProject: inventory
- Nhấp
Generate
Kiểm tra các tệp kết nối Story Agent → Inventory MCP
Generator connection tạo ra/cập nhật các tệp sau:
Thư mụcpackages/
Thư mụccommon/
Thư mụcagent_connection/
Thư mụcdungeon_adventure_agent_connection/
Thư mụccore/
- agentcore_endpoints.py Giải quyết ARN/URL không phụ thuộc framework
- agentcore_mcp_transport.py MCP transport không phụ thuộc framework
- agentcore_mcp_client_strands.py Strands MCP client bọc transport
Thư mụcauth/ SigV4 / session-forwarding
httpx.Authkhông phụ thuộc framework- …
Thư mụcapp/
- inventory_mcp_server_client_strands.py Strands client để kết nối với Inventory MCP server
- __init__.py Tái xuất các client theo kết nối
Thư mụcstory/
Thư mụcdungeon_adventure_story/agent/
- agent.py Được sửa đổi để nhập và sử dụng MCP client
Generator:
- Tạo một dự án Python
agent_connectiondùng chung (nếu chưa tồn tại) vớiAgentCoreMCPClientStrandscốt lõi - Tạo một lớp
InventoryMcpServerClientStrandsxử lý kết nối đến MCP server cả cục bộ (HTTP trực tiếp) và khi triển khai (thông qua AgentCore với xác thực IAM) - Biến đổi
agent.pyđể nhập client, tạo một instance và kết nối các công cụ của MCP server vào agent - Thêm dự án
agent_connectionnhư một phụ thuộc workspace của dự án story - Cập nhật mục tiêu
devđể tự động khởi động MCP server khi chạy cục bộ
Để biết thêm chi tiết, hãy tham khảo hướng dẫn kết nối Python Agent với MCP.
Game UI: Kết nối với Story Agent
Phần tiêu đề “Game UI: Kết nối với Story Agent”Hãy kết nối Game UI của chúng ta với Story Agent. Vì agent sử dụng AG-UI, generator connection kết nối CopilotKit: một thành phần chat có chủ đề và một HttpAgent @ag-ui/client sẵn sàng để hiển thị.
pnpm nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-ui --targetProject=story --no-interactiveyarn nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-ui --targetProject=story --no-interactivenpx nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-ui --targetProject=story --no-interactivebunx nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-ui --targetProject=story --no-interactiveBạn cũng có thể thực hiện chạy thử để xem những tệp nào sẽ bị thay đổi
pnpm nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-ui --targetProject=story --no-interactive --dry-runyarn nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-ui --targetProject=story --no-interactive --dry-runnpx nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-ui --targetProject=story --no-interactive --dry-runbunx nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-ui --targetProject=story --no-interactive --dry-run- Cài đặt Nx Console VSCode Plugin nếu bạn chưa cài đặt
- Mở Nx Console trong VSCode
- Nhấp
Generate (UI)trong phần "Common Nx Commands" - Tìm kiếm
@aws/nx-plugin - connection - Điền các tham số bắt buộc
- sourceProject: @dungeon-adventure/game-ui
- targetProject: story
- Nhấp
Generate
Kiểm tra các tệp kết nối UI → Story Agent
Generator connection tạo ra/cập nhật các tệp sau:
Thư mụcpackages/
Thư mụcgame-ui/
Thư mụcsrc/
Thư mụccomponents/
- AguiProvider.tsx
CopilotKitProvidervới mọi agent AG-UI được kết nối đã đăng ký Thư mụccopilot/
- index.tsx
CopilotChat/CopilotSidebar/CopilotPopupcó chủ đề Shadcn - ShadcnAssistantMessage.tsx, ShadcnUserMessage.tsx, ShadcnChatInput.tsx, ShadcnCursor.tsx, copilot.css
- index.tsx
- AguiProvider.tsx
Thư mụchooks/
- useAguiStoryAgent.tsx Xây dựng một
HttpAgent, tiêm token bearer Cognito và đệmthreadIdđến session id 33 ký tự của AgentCore
- useAguiStoryAgent.tsx Xây dựng một
- main.tsx Bọc
<App />trong<AguiProvider>
Generator:
- Phát hiện
uxcủa website React (Shadcn ở đây) và cung cấp các thành phần chat phù hợp. - Đăng ký mọi agent được kết nối trên một
CopilotKitProviderduy nhất — chạy lại cho một agent khác chỉ thêm một hook khác. - Đọc ARN runtime của agent từ Runtime Configuration, xây dựng URL gọi AgentCore và đính kèm token bearer Cognito cùng với header session id AgentCore.
Để biết thêm chi tiết, hãy tham khảo hướng dẫn kết nối React với AG-UI.
Kết nối Game API và Inventory MCP server với cơ sở dữ liệu
Phần tiêu đề “Kết nối Game API và Inventory MCP server với cơ sở dữ liệu”Cả Game API và Inventory MCP server đều đọc và ghi bảng DynamoDB của chúng ta, vì vậy hãy kết nối chúng với dự án DungeonDb. Generator connection phát hiện rằng mục tiêu là một dự án ts#dynamodb và kết nối mục tiêu dev của mỗi dự án nguồn để tự động khởi động DynamoDB Local.
pnpm nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-api --targetProject=@dungeon-adventure/dungeon-db --no-interactiveyarn nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-api --targetProject=@dungeon-adventure/dungeon-db --no-interactivenpx nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-api --targetProject=@dungeon-adventure/dungeon-db --no-interactivebunx nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-api --targetProject=@dungeon-adventure/dungeon-db --no-interactiveBạn cũng có thể thực hiện chạy thử để xem những tệp nào sẽ bị thay đổi
pnpm nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-api --targetProject=@dungeon-adventure/dungeon-db --no-interactive --dry-runyarn nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-api --targetProject=@dungeon-adventure/dungeon-db --no-interactive --dry-runnpx nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-api --targetProject=@dungeon-adventure/dungeon-db --no-interactive --dry-runbunx nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/game-api --targetProject=@dungeon-adventure/dungeon-db --no-interactive --dry-run- Cài đặt Nx Console VSCode Plugin nếu bạn chưa cài đặt
- Mở Nx Console trong VSCode
- Nhấp
Generate (UI)trong phần "Common Nx Commands" - Tìm kiếm
@aws/nx-plugin - connection - Điền các tham số bắt buộc
- sourceProject: @dungeon-adventure/game-api
- targetProject: @dungeon-adventure/dungeon-db
- Nhấp
Generate
pnpm nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/inventory --targetProject=@dungeon-adventure/dungeon-db --no-interactiveyarn nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/inventory --targetProject=@dungeon-adventure/dungeon-db --no-interactivenpx nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/inventory --targetProject=@dungeon-adventure/dungeon-db --no-interactivebunx nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/inventory --targetProject=@dungeon-adventure/dungeon-db --no-interactiveBạn cũng có thể thực hiện chạy thử để xem những tệp nào sẽ bị thay đổi
pnpm nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/inventory --targetProject=@dungeon-adventure/dungeon-db --no-interactive --dry-runyarn nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/inventory --targetProject=@dungeon-adventure/dungeon-db --no-interactive --dry-runnpx nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/inventory --targetProject=@dungeon-adventure/dungeon-db --no-interactive --dry-runbunx nx g @aws/nx-plugin:connection --sourceProject=@dungeon-adventure/inventory --targetProject=@dungeon-adventure/dungeon-db --no-interactive --dry-run- Cài đặt Nx Console VSCode Plugin nếu bạn chưa cài đặt
- Mở Nx Console trong VSCode
- Nhấp
Generate (UI)trong phần "Common Nx Commands" - Tìm kiếm
@aws/nx-plugin - connection - Điền các tham số bắt buộc
- sourceProject: @dungeon-adventure/inventory
- targetProject: @dungeon-adventure/dungeon-db
- Nhấp
Generate
Game UI: Cơ sở hạ tầng
Phần tiêu đề “Game UI: Cơ sở hạ tầng”Hãy tạo dự án con cuối cùng cho cơ sở hạ tầng CDK.
pnpm nx g @aws/nx-plugin:ts#infra --name=infra --no-interactiveyarn nx g @aws/nx-plugin:ts#infra --name=infra --no-interactivenpx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactivebunx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactiveBạn cũng có thể thực hiện chạy thử để xem những tệp nào sẽ bị thay đổi
pnpm nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-runyarn nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-runnpx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-runbunx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run- Cài đặt Nx Console VSCode Plugin nếu bạn chưa cài đặt
- Mở Nx Console trong VSCode
- Nhấp
Generate (UI)trong phần "Common Nx Commands" - Tìm kiếm
@aws/nx-plugin - ts#infra - Điền các tham số bắt buộc
- name: infra
- Nhấp
Generate
Bạn sẽ thấy một số tệp mới xuất hiện/thay đổi trong cây tệp của mình.
Kiểm tra chi tiết các tệp ts#infra được tạo ra
Generator ts#infra tạo ra/cập nhật các tệp sau. Hãy xem xét một số tệp quan trọng được đánh dấu trong cây tệp:
Thư mụcpackages/
Thư mụccommon/
Thư mụcconstructs/
Thư mụcsrc/
Thư mụccore/
- checkov.ts
- index.ts
Thư mụcinfra
Thư mụcsrc/
Thư mụcstages/
- application-stage.ts các cdk stack được định nghĩa ở đây
Thư mụcstacks/
- application-stack.ts các tài nguyên cdk được định nghĩa ở đây
- main.ts điểm vào định nghĩa tất cả các stage
- cdk.json
- checkov.yml
- project.json
- …
- package.json
- tsconfig.json thêm references
- tsconfig.base.json thêm alias
import { ApplicationStage } from './stages/application-stage.js';import { App } from '@dungeon-adventure/common-constructs';
const app = new App();
// Use this to deploy your own sandbox environment (assumes your CLI credentials)new ApplicationStage(app, 'dungeon-adventure-infra-sandbox', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, },});
app.synth();Đây là điểm vào cho ứng dụng CDK của bạn.
import { Stack, StackProps } from 'aws-cdk-lib';import { Construct } from 'constructs';
export class ApplicationStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props);
// The code that defines your stack goes here }}Hãy khởi tạo các CDK construct của chúng ta để xây dựng trò chơi phiêu lưu ngục tối.
Nhiệm vụ 3: Cập nhật cơ sở hạ tầng của chúng ta
Phần tiêu đề “Nhiệm vụ 3: Cập nhật cơ sở hạ tầng của chúng ta”Hãy cập nhật packages/infra/src/stacks/application-stack.ts để khởi tạo một số construct đã tạo của chúng ta:
import { GameApi, GameUI, InventoryMcpServer, StoryAgent, UserIdentity,} from '@dungeon-adventure/common-constructs';import { Stack, StackProps, CfnOutput } from 'aws-cdk-lib';import { Construct } from 'constructs';export class ApplicationStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props);
const userIdentity = new UserIdentity(this, 'UserIdentity');
const gameApi = new GameApi(this, 'GameApi', { integrations: GameApi.defaultIntegrations(this).build(), });
const mcpServer = new InventoryMcpServer(this, 'InventoryMcpServer');
// Use Cognito for user authentication with the agent const storyAgent = new StoryAgent(this, 'StoryAgent', { identity: userIdentity, });
new CfnOutput(this, 'StoryAgentArn', { value: storyAgent.agentCoreRuntime.agentRuntimeArn, }); new CfnOutput(this, 'InventoryMcpArn', { value: mcpServer.agentCoreRuntime.agentRuntimeArn, });
// Grant the agent permissions to invoke our mcp server mcpServer.grantInvokeAccess(storyAgent);
// Grant the authenticated role access to invoke the api gameApi.grantInvokeAccess(userIdentity.identityPool.authenticatedRole);
new GameUI(this, 'GameUI'); }}import { Stack, StackProps } from 'aws-cdk-lib';import { GameApi, GameUI, InventoryMcpServer, StoryAgent, UserIdentity,} from '@dungeon-adventure/common-constructs';import { Stack, StackProps, CfnOutput } from 'aws-cdk-lib';import { Construct } from 'constructs';
export class ApplicationStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props);
// The code that defines your stack goes here const userIdentity = new UserIdentity(this, 'UserIdentity');
const gameApi = new GameApi(this, 'GameApi', { integrations: GameApi.defaultIntegrations(this).build(), });
const mcpServer = new InventoryMcpServer(this, 'InventoryMcpServer');
// Use Cognito for user authentication with the agent const storyAgent = new StoryAgent(this, 'StoryAgent', { identity: userIdentity, });
new CfnOutput(this, 'StoryAgentArn', { value: storyAgent.agentCoreRuntime.agentRuntimeArn, }); new CfnOutput(this, 'InventoryMcpArn', { value: mcpServer.agentCoreRuntime.agentRuntimeArn, });
// Grant the agent permissions to invoke our mcp server mcpServer.grantInvokeAccess(storyAgent);
// Grant the authenticated role access to invoke the api gameApi.grantInvokeAccess(userIdentity.identityPool.authenticatedRole);
new GameUI(this, 'GameUI'); }}Nhiệm vụ 4: Build mã
Phần tiêu đề “Nhiệm vụ 4: Build mã”Các lệnh Nx
Mục tiêu đơn lẻ và nhiều mục tiêu
Phần tiêu đề “Mục tiêu đơn lẻ và nhiều mục tiêu”Lệnh run-many sẽ chạy một mục tiêu trên nhiều dự án con được liệt kê (--all sẽ nhắm đến tất cả). Điều này đảm bảo các phụ thuộc được thực thi theo đúng thứ tự.
Bạn cũng có thể kích hoạt một build (hoặc bất kỳ tác vụ nào khác) cho một mục tiêu dự án đơn lẻ bằng cách chạy mục tiêu trực tiếp trên dự án. Ví dụ, để build dự án @dungeon-adventure/infra, chạy lệnh sau:
pnpm nx build infrayarn nx build infranpx nx build infrabunx nx build infraBạn cũng có thể bỏ qua phạm vi và sử dụng cú pháp viết tắt Nx nếu bạn thích:
pnpm nx build infrayarn nx build infranpx nx build infrabunx nx build infraTrực quan hóa các phụ thuộc của bạn
Phần tiêu đề “Trực quan hóa các phụ thuộc của bạn”Để trực quan hóa các phụ thuộc của bạn, chạy:
pnpm nx graphyarn nx graphnpx nx graphbunx nx graph
Bộ nhớ đệm
Phần tiêu đề “Bộ nhớ đệm”Nx dựa vào bộ nhớ đệm để bạn có thể tái sử dụng các artifact từ các build trước đó nhằm tăng tốc độ phát triển. Có một số cấu hình cần thiết để làm cho điều này hoạt động đúng và có thể có những trường hợp bạn muốn thực hiện một build mà không sử dụng bộ nhớ đệm. Để làm điều đó, chỉ cần thêm đối số --skip-nx-cache vào lệnh của bạn. Ví dụ:
pnpm nx build infra --skip-nx-cacheyarn nx build infra --skip-nx-cachenpx nx build infra --skip-nx-cachebunx nx build infra --skip-nx-cacheNếu vì lý do nào đó bạn muốn xóa bộ nhớ đệm (được lưu trong thư mục .nx), bạn có thể chạy lệnh sau:
pnpm nx resetyarn nx resetnpx nx resetbunx nx resetSử dụng dòng lệnh, chạy lệnh sau để sửa các vấn đề lint trước:
pnpm lintyarn lintnpm run lintbun lintSau đó, chạy lệnh sau để build đầy đủ:
pnpm buildyarn buildnpm run buildbun buildBạn sẽ được nhắc với nội dung sau:
NX The workspace is out of sync
[@nx/js:typescript-sync]: Some TypeScript configuration files are missing project references to the projects they depend on or contain outdated project references.
This will result in an error in CI.
? Would you like to sync the identified changes to get your workspace up to date? …Yes, sync the changes and run the tasksNo, run the tasks without syncing the changesThông báo này cho biết NX đã phát hiện một số tệp có thể được cập nhật tự động cho bạn. Trong trường hợp này, nó đề cập đến các tệp tsconfig.json không có Typescript references được thiết lập trên các dự án được tham chiếu.
Chọn tùy chọn Yes, sync the changes and run the tasks để tiếp tục. Bạn sẽ nhận thấy tất cả các lỗi import liên quan đến IDE của bạn được tự động giải quyết khi generator sync sẽ tự động thêm các typescript references còn thiếu!
Tất cả các artifact đã build hiện có sẵn trong thư mục dist/ nằm ở gốc của monorepo. Đây là thực hành tiêu chuẩn khi sử dụng các dự án được tạo bởi @aws/nx-plugin vì nó không làm ô nhiễm cây tệp của bạn với các tệp được tạo ra. Trong trường hợp bạn muốn dọn dẹp các tệp của mình, hãy xóa thư mục dist/ mà không lo lắng về các artifact build bị rải rác khắp cây tệp.
Chúc mừng! Bạn đã tạo tất cả các dự án con cần thiết để bắt đầu triển khai phần cốt lõi của trò chơi AI Dungeon Adventure của chúng ta. 🎉🎉🎉