代理式AI地牢游戏
模块 1:Monorepo 设置
Section titled “模块 1:Monorepo 设置”我们将从创建新的 monorepo 开始。在目标目录中运行以下命令:
npx create-nx-workspace@21.6.4 dungeon-adventure --pm=pnpm --preset=@aws/nx-plugin --iacProvider=CDK --ci=skip --aiAgents
npx create-nx-workspace@21.6.4 dungeon-adventure --pm=yarn --preset=@aws/nx-plugin --iacProvider=CDK --ci=skip --aiAgents
npx create-nx-workspace@21.6.4 dungeon-adventure --pm=npm --preset=@aws/nx-plugin --iacProvider=CDK --ci=skip --aiAgents
npx create-nx-workspace@21.6.4 dungeon-adventure --pm=bun --preset=@aws/nx-plugin --iacProvider=CDK --ci=skip --aiAgents
这将在 dungeon-adventure
目录中设置 NX monorepo,您可以在 vscode 中打开。其结构应如下所示:
文件夹.nx/
- …
文件夹.vscode/
- …
文件夹node_modules/
- …
文件夹packages/ 此处存放所有子项目
- …
- .gitignore
- .npmrc
- .prettierignore
- .prettierrc
- nx.json 配置 Nx CLI 和 monorepo 默认值
- package.json 定义所有 node 依赖项
- pnpm-lock.yaml 或 bun.lock, yarn.lock, package-lock.json(取决于包管理器)
- pnpm-workspace.yaml(如果使用 pnpm)
- README.md
- tsconfig.base.json 所有基于 node 的子项目继承此配置
- tsconfig.json
- aws-nx-plugin.config.mts 配置 Nx Plugin for AWS
现在我们可以使用 @aws/nx-plugin
开始创建不同的子项目。
游戏 API
Section titled “游戏 API”首先创建游戏 API。通过以下步骤创建名为 GameApi
的 tRPC API:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)
在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - ts#trpc-api
- 填写必需参数
- name: GameApi
- 点击
Generate
pnpm nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
yarn nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
npx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
bunx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
您还可以执行试运行以查看哪些文件会被更改
pnpm nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
您应该看到文件树中出现了新文件。
ts#trpc-api 更新文件
以下是 ts#trpc-api
生成器生成的所有文件列表。我们将重点查看文件树中高亮的关键文件:
文件夹packages/
文件夹common/
文件夹constructs/
文件夹src/
文件夹app/ 应用特定 CDK 构造体
文件夹apis/
- game-api.ts 创建 tRPC API 的 CDK 构造体
- index.ts
- …
- index.ts
文件夹core/ 通用 CDK 构造体
文件夹api/
- rest-api.ts API Gateway Rest API 的基础 CDK 构造体
- trpc-utils.ts tRPC API CDK 构造体的工具
- utils.ts API 构造体的工具
- index.ts
- runtime-config.ts
- index.ts
- project.json
- …
文件夹types/ 共享类型
文件夹src/
- index.ts
- runtime-config.ts CDK 和网站共用的接口定义
- project.json
- …
文件夹game-api/ tRPC API
文件夹src/
文件夹client/ 用于 TS 机器间调用的原生客户端
- index.ts
- sigv4.ts
文件夹middleware/ Powertools 工具链
- error.ts
- index.ts
- logger.ts
- metrics.ts
- tracer.ts
文件夹schema/ API 输入输出定义
- echo.ts
文件夹procedures/ API 过程/路由的具体实现
- echo.ts
- index.ts
- init.ts 设置上下文和中间件
- local-server.ts 本地运行 tRPC 服务器时使用
- router.ts Lambda 处理程序的入口点,定义所有过程
- project.json
- …
- eslint.config.mjs
- vitest.workspace.ts
查看部分关键文件:
import { awsLambdaRequestHandler, CreateAWSLambdaContextOptions,} from '@trpc/server/adapters/aws-lambda';import { echo } from './procedures/echo.js';import { t } from './init.js';import { APIGatewayProxyEvent } from 'aws-lambda';
export const router = t.router;
export const appRouter = router({ echo,});
export const handler = awsLambdaRequestHandler({ router: appRouter, createContext: ( ctx: CreateAWSLambdaContextOptions<APIGatewayProxyEvent>, ) => ctx, responseMeta: () => ({ headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': '*', }, }),});
export type AppRouter = typeof appRouter;
路由文件定义了 tRPC API 的入口点,您需要在此声明所有 API 方法。如上所示,我们有一个名为 echo
的方法,其实现位于 ./procedures/echo.ts
文件。
import { publicProcedure } from '../init.js';import { EchoInputSchema, EchoOutputSchema,} from '../schema/echo.js';
export const echo = publicProcedure .input(EchoInputSchema) .output(EchoOutputSchema) .query((opts) => ({ result: opts.input.message }));
该文件是 echo
方法的实现,通过声明输入输出数据结构实现强类型。
import { z } from 'zod';
export const EchoInputSchema = z.object({ message: z.string(),});
export type IEchoInput = z.TypeOf<typeof EchoInputSchema>;
export const EchoOutputSchema = z.object({ result: z.string(),});
export type IEchoOutput = z.TypeOf<typeof EchoOutputSchema>;
所有 tRPC 模式定义均使用 Zod 定义,并通过 z.TypeOf
语法导出为 TypeScript 类型。
import { Construct } from 'constructs';import * as url from 'url';import { Code, Runtime, Function, FunctionProps, Tracing,} from 'aws-cdk-lib/aws-lambda';import { AuthorizationType, Cors, LambdaIntegration,} from 'aws-cdk-lib/aws-apigateway';import { Duration, Stack } from 'aws-cdk-lib';import { PolicyDocument, PolicyStatement, Effect, AccountPrincipal, AnyPrincipal,} from 'aws-cdk-lib/aws-iam';import { IntegrationBuilder, RestApiIntegration,} from '../../core/api/utils.js';import { RestApi } from '../../core/api/rest-api.js';import { Procedures, routerToOperations } from '../../core/api/trpc-utils.js';import { AppRouter, appRouter } from ':dungeon-adventure/game-api';
// 所有 API 操作名称的字符串联合类型type Operations = Procedures<AppRouter>;
/** * 创建 GameApi 构造体的属性 * * @template TIntegrations - 操作名称到集成的映射 */export interface GameApiProps< TIntegrations extends Record<Operations, RestApiIntegration>,> { /** * 操作名称到 API Gateway 集成的映射 */ integrations: TIntegrations;}
/** * 用于创建和配置 AWS API Gateway REST API 的 CDK 构造体 * @template TIntegrations - 操作名称到集成的映射 */export class GameApi< TIntegrations extends Record<Operations, RestApiIntegration>,> extends RestApi<Operations, TIntegrations> { /** * 为所有操作创建默认集成,将每个操作实现为独立的 lambda 函数 * * @param scope - CDK 构造体作用域 * @returns 包含默认 lambda 集成的 IntegrationBuilder */ public static defaultIntegrations = (scope: Construct) => { return IntegrationBuilder.rest({ 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, environment: { AWS_CONNECTION_REUSE_ENABLED: '1', }, } satisfies FunctionProps, buildDefaultIntegration: (op, props: FunctionProps) => { const handler = new Function(scope, `GameApi${op}Handler`, props); return { handler, integration: new LambdaIntegration(handler) }; }, }); };
constructor( scope: Construct, id: string, props: GameApiProps<TIntegrations>, ) { super(scope, id, { apiName: 'GameApi', defaultMethodOptions: { authorizationType: AuthorizationType.IAM, }, defaultCorsPreflightOptions: { allowOrigins: Cors.ALL_ORIGINS, allowMethods: Cors.ALL_METHODS, }, policy: new PolicyDocument({ statements: [ // 允许部署账户中的 AWS 凭证调用 API new PolicyStatement({ effect: Effect.ALLOW, principals: [new AccountPrincipal(Stack.of(scope).account)], actions: ['execute-api:Invoke'], resources: ['execute-api:/*'], }), // 开放 OPTIONS 方法以允许浏览器进行未经身份验证的预检请求 new PolicyStatement({ effect: Effect.ALLOW, principals: [new AnyPrincipal()], actions: ['execute-api:Invoke'], resources: ['execute-api:/*/OPTIONS/*'], }), ], }), operations: routerToOperations(appRouter), ...props, }); }}
这是定义 GameApi 的 CDK 构造体。其 defaultIntegrations
方法会自动为 tRPC API 中的每个过程创建 lambda 函数,指向已打包的 API 实现。这意味着在 cdk synth
时不会进行打包(与使用 NodeJsFunction 不同),因为我们已经通过后端项目的构建目标完成了打包。
故事代理:Python 项目
Section titled “故事代理:Python 项目”现在创建故事代理。首先创建 Python 项目:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)
在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - py#project
- 填写必需参数
- name: story
- 点击
Generate
pnpm nx g @aws/nx-plugin:py#project --name=story --no-interactive
yarn nx g @aws/nx-plugin:py#project --name=story --no-interactive
npx nx g @aws/nx-plugin:py#project --name=story --no-interactive
bunx nx g @aws/nx-plugin:py#project --name=story --no-interactive
您还可以执行试运行以查看哪些文件会被更改
pnpm nx g @aws/nx-plugin:py#project --name=story --no-interactive --dry-run
yarn nx g @aws/nx-plugin:py#project --name=story --no-interactive --dry-run
npx nx g @aws/nx-plugin:py#project --name=story --no-interactive --dry-run
bunx nx g @aws/nx-plugin:py#project --name=story --no-interactive --dry-run
您应该看到文件树中出现了新文件。
py#project 更新文件
以下是 py#project
生成器生成的文件列表:
文件夹.venv/ monorepo 共享虚拟环境
- …
文件夹packages/
文件夹story/
文件夹dungeon_adventure_story/ Python 模块
- hello.py 示例 Python 文件(可忽略)
文件夹tests/
- …
- .python-version
- pyproject.toml
- project.json
- .python-version 固定 UV Python 版本
- pyproject.toml
- uv.lock
这配置了一个使用 UV Workspace 和共享虚拟环境的 Python 项目。
故事代理:Strands 代理
Section titled “故事代理:Strands 代理”接下来使用 py#strands-agent
生成器为项目添加 Strands 代理:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)
在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - py#strands-agent
- 填写必需参数
- project: story
- 点击
Generate
pnpm nx g @aws/nx-plugin:py#strands-agent --project=story --no-interactive
yarn nx g @aws/nx-plugin:py#strands-agent --project=story --no-interactive
npx nx g @aws/nx-plugin:py#strands-agent --project=story --no-interactive
bunx nx g @aws/nx-plugin:py#strands-agent --project=story --no-interactive
您还可以执行试运行以查看哪些文件会被更改
pnpm nx g @aws/nx-plugin:py#strands-agent --project=story --no-interactive --dry-run
yarn nx g @aws/nx-plugin:py#strands-agent --project=story --no-interactive --dry-run
npx nx g @aws/nx-plugin:py#strands-agent --project=story --no-interactive --dry-run
bunx nx g @aws/nx-plugin:py#strands-agent --project=story --no-interactive --dry-run
您应该看到文件树中出现了新文件。
py#strands-agent 更新文件
以下是 py#strands-agent
生成器生成的文件列表:
文件夹packages/
文件夹story/
文件夹dungeon_adventure_story/ Python 模块
文件夹agent/
- main.py Bedrock AgentCore Runtime 的代理入口点
- agent.py 定义示例代理和工具
- agentcore_mcp_client.py 与 MCP 服务器交互的客户端工具
- Dockerfile 部署到 AgentCore Runtime 的 Docker 镜像定义
文件夹common/constructs/
文件夹src
文件夹core/agent-core/
- runtime.ts 部署到 AgentCore Runtime 的通用构造体
文件夹app/agents/story-agent/
- story-agent.ts 将故事代理部署到 AgentCore Runtime 的构造体
查看部分关键文件:
from contextlib import contextmanager
from strands import Agent, toolfrom strands_tools import current_time
# 定义自定义工具@tooldef add(a: int, b: int) -> int: return a + b
@contextmanagerdef get_agent(session_id: str): yield Agent( system_prompt="""你是加法巫师。使用 'add' 工具处理加法任务。将工具称为你的 '法术书'。""", tools=[add, current_time], )
创建了一个示例 Strands 代理并定义了加法工具。
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from .agent import get_agent
app = BedrockAgentCoreApp()
@app.entrypointasync def invoke(payload, context): """代理调用的处理程序""" prompt = payload.get( "prompt", "输入中未找到提示,请引导用户创建包含 prompt 键的 json 有效载荷" )
with get_agent(session_id=context.session_id) as agent: stream = agent.stream_async(prompt) async for event in stream: print(event) yield (event)
if __name__ == "__main__": app.run()
这是代理的入口点,使用 Amazon Bedrock AgentCore SDK 配置。利用 Strands 的流式支持,在事件发生时将事件流式传输回客户端。
import { Lazy, Names } from 'aws-cdk-lib';import { DockerImageAsset, Platform } from 'aws-cdk-lib/aws-ecr-assets';import { Construct } from 'constructs';import { execSync } from 'child_process';import * as path from 'path';import * as url from 'url';import { AgentCoreRuntime, AgentCoreRuntimeProps,} from '../../../core/agent-core/runtime.js';
export type StoryAgentProps = Omit< AgentCoreRuntimeProps, 'runtimeName' | 'serverProtocol' | 'containerUri'>;
export class StoryAgent extends Construct { public readonly dockerImage: DockerImageAsset; public readonly agentCoreRuntime: AgentCoreRuntime;
constructor(scope: Construct, id: string, props?: StoryAgentProps) { super(scope, id);
this.dockerImage = new DockerImageAsset(this, 'DockerImage', { platform: Platform.LINUX_ARM64, directory: path.dirname(url.fileURLToPath(new URL(import.meta.url))), extraHash: execSync( `docker inspect dungeon-adventure-story-agent:latest --format '{{.Id}}'`, { encoding: 'utf-8' }, ).trim(), });
this.agentCoreRuntime = new AgentCoreRuntime(this, 'StoryAgent', { runtimeName: Lazy.string({ produce: () => Names.uniqueResourceName(this.agentCoreRuntime, { maxLength: 40 }), }), serverProtocol: 'HTTP', containerUri: this.dockerImage.imageUri, ...props, }); }}
该配置使用 CDK 的 DockerImageAsset
将代理 Docker 镜像上传到 ECR,并通过 AgentCore Runtime 托管。
您可能注意到额外的 Dockerfile
- 这引用了 story
项目的 Docker 镜像,允许我们将 Dockerfile 与代理源代码放在一起。
库存:TypeScript 项目
Section titled “库存:TypeScript 项目”现在创建 MCP 服务器,为故事代理提供管理玩家库存的工具。
首先创建 TypeScript 项目:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)
在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - ts#project
- 填写必需参数
- name: inventory
- 点击
Generate
pnpm nx g @aws/nx-plugin:ts#project --name=inventory --no-interactive
yarn nx g @aws/nx-plugin:ts#project --name=inventory --no-interactive
npx nx g @aws/nx-plugin:ts#project --name=inventory --no-interactive
bunx nx g @aws/nx-plugin:ts#project --name=inventory --no-interactive
您还可以执行试运行以查看哪些文件会被更改
pnpm nx g @aws/nx-plugin:ts#project --name=inventory --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#project --name=inventory --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#project --name=inventory --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#project --name=inventory --no-interactive --dry-run
这将创建一个空的 TypeScript 项目。
ts#project 更新文件
以下是 ts#project
生成器生成的文件列表:
文件夹packages/
文件夹inventory/
文件夹src/
- index.ts 包含示例函数的入口点
- project.json 项目配置
- eslint.config.mjs 代码检查配置
- vite.config.ts 测试配置
- tsconfig.json 项目基础 TypeScript 配置
- tsconfig.lib.json 针对编译和打包的 TypeScript 配置
- tsconfig.spec.json 测试的 TypeScript 配置
- tsconfig.base.json 更新以配置其他项目引用该项目的别名
库存:MCP 服务器
Section titled “库存:MCP 服务器”接下来为 TypeScript 项目添加 MCP 服务器:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)
在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - ts#mcp-server
- 填写必需参数
- project: inventory
- 点击
Generate
pnpm nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactive
yarn nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactive
npx nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactive
bunx nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactive
您还可以执行试运行以查看哪些文件会被更改
pnpm nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#mcp-server --project=inventory --no-interactive --dry-run
这将添加
ts#mcp-server 更新文件
以下是 ts#mcp-server
生成器生成的文件列表:
文件夹packages/
文件夹inventory/
文件夹src/mcp-server/
- server.ts 创建 MCP 服务器
文件夹tools/
- add.ts 示例工具
文件夹resources/
- sample-guidance.ts 示例资源
- stdio.ts 使用 STDIO 传输的 MCP 入口点
- http.ts 使用可流式 HTTP 传输的 MCP 入口点
- Dockerfile 构建部署到 AgentCore Runtime 的镜像
- rolldown.config.ts 配置 MCP 服务器打包以部署到 AgentCore
文件夹common/constructs/
文件夹src
文件夹app/mcp-servers/inventory-mcp-server/
- inventory-mcp-server.ts 将库存 MCP 服务器部署到 AgentCore Runtime 的构造体
游戏 UI:网站
Section titled “游戏 UI:网站”现在创建与游戏交互的 UI。通过以下步骤创建名为 GameUI
的网站:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)
在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - ts#react-website
- 填写必需参数
- name: GameUI
- 点击
Generate
pnpm nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive
yarn nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive
npx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive
bunx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive
您还可以执行试运行以查看哪些文件会被更改
pnpm nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-run
您应该看到文件树中出现了新文件。
ts#react-website 更新文件
以下是 ts#react-website
生成器生成的文件列表。我们将重点查看文件树中高亮的关键文件:
文件夹packages/
文件夹common/
文件夹constructs/
文件夹src/
文件夹app/ 应用特定 CDK 构造体
文件夹static-websites/
- game-ui.ts 创建游戏 UI 的 CDK 构造体
文件夹core/
- static-website.ts 通用静态网站构造体
文件夹game-ui/
文件夹public/
- …
文件夹src/
文件夹components/
文件夹AppLayout/
- index.ts 整体页面布局:页头、页脚、侧边栏等
- navitems.ts 侧边栏导航项
文件夹hooks/
- useAppLayout.tsx 动态设置通知、页面样式等
文件夹routes/ @tanstack/react-router 基于文件的路由
- index.tsx 根 ’/’ 页面重定向到 ‘/welcome’
- __root.tsx 所有页面使用此组件作为基础
文件夹welcome/
- index.tsx
- config.ts
- main.tsx React 入口点
- routeTree.gen.ts 由 @tanstack/react-router 自动更新
- styles.css
- index.html
- project.json
- vite.config.ts
- …
import * as url from 'url';import { Construct } from 'constructs';import { StaticWebsite } from '../../core/index.js';
export class GameUI extends StaticWebsite { constructor(scope: Construct, id: string) { super(scope, id, { websiteName: 'GameUI', websiteFilePath: url.fileURLToPath( new URL( '../../../../../../dist/packages/game-ui/bundle', import.meta.url, ), ), }); }}
这是定义 GameUI 的 CDK 构造体。已配置指向 Vite 构建产出的文件路径,这意味着在 build
时,打包发生在 game-ui 项目的构建目标中,其输出在此处使用。
import React from 'react';import { createRoot } from 'react-dom/client';import { I18nProvider } from '@cloudscape-design/components/i18n';import messages from '@cloudscape-design/components/i18n/messages/all.en';import { RouterProvider, createRouter } from '@tanstack/react-router';import { routeTree } from './routeTree.gen';
import '@cloudscape-design/global-styles/index.css';
const router = createRouter({ routeTree });
// 为类型安全注册路由实例declare module '@tanstack/react-router' { interface Register { router: typeof router; }}
const root = document.getElementById('root');root && createRoot(root).render( <React.StrictMode> <I18nProvider locale="en" messages={[messages]}> <RouterProvider router={router} /> </I18nProvider> </React.StrictMode>, );
这是挂载 React 的入口点。配置了 @tanstack/react-router
的文件路由,只要开发服务器运行,在 routes
文件夹中创建文件时,@tanstack/react-router
会自动生成样板文件并更新 routeTree.gen.ts
文件。该文件以类型安全的方式维护所有路由,使用 <Link>
时 to
选项仅显示有效路由。更多信息请参考 @tanstack/react-router
文档。
import { ContentLayout, Header, SpaceBetween, Container,} from '@cloudscape-design/components';import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/welcome/')({ component: RouteComponent,});
function RouteComponent() { return ( <ContentLayout header={<Header>Welcome</Header>}> <SpaceBetween size="l"> <Container>欢迎来到新的 Cloudscape 网站!</Container> </SpaceBetween> </ContentLayout> );}
导航到 /welcome
路由时渲染的组件。只要开发服务器运行,@tanstack/react-router
会在创建/移动此文件时管理 Route
。这将在本教程后续部分展示。
游戏 UI:身份验证
Section titled “游戏 UI:身份验证”现在通过以下步骤配置游戏 UI 要求通过 Amazon Cognito 进行身份验证:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)
在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - ts#react-website#auth
- 填写必需参数
- cognitoDomain: game-ui
- project: @dungeon-adventure/game-ui
- allowSignup: true
- 点击
Generate
pnpm nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
yarn nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
npx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
bunx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
您还可以执行试运行以查看哪些文件会被更改
pnpm nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
您应该看到文件树中出现了新文件或变更。
ts#react-website#auth 更新文件
以下是 ts#react-website#auth
生成器生成/更新的文件列表。我们将重点查看文件树中高亮的关键文件:
文件夹packages/
文件夹common/
文件夹constructs/
文件夹src/
文件夹core/
- user-identity.ts 创建用户/身份池的 CDK 构造体
文件夹types/
文件夹src/
- runtime-config.ts 更新添加 cognitoProps
文件夹game-ui/
文件夹src/
文件夹components/
文件夹AppLayout/
- index.tsx 在页头添加已登录用户/注销功能
文件夹CognitoAuth/
- index.ts 管理 Cognito 登录
文件夹RuntimeConfig/
- index.tsx 获取
runtime-config.json
并通过上下文提供给子组件
- index.tsx 获取
文件夹hooks/
- useRuntimeConfig.tsx
- main.tsx 更新添加 Cognito
import CognitoAuth from './components/CognitoAuth';import RuntimeConfigProvider from './components/RuntimeConfig';import React from 'react';import { createRoot } from 'react-dom/client';import { I18nProvider } from '@cloudscape-design/components/i18n';import messages from '@cloudscape-design/components/i18n/messages/all.en';import { RouterProvider, createRouter } from '@tanstack/react-router';import { routeTree } from './routeTree.gen';import '@cloudscape-design/global-styles/index.css';const router = createRouter({ routeTree });// 为类型安全注册路由实例declare module '@tanstack/react-router' { interface Register { router: typeof router; }}const root = document.getElementById('root');root && createRoot(root).render( <React.StrictMode> <I18nProvider locale="en" messages={[messages]}> <RuntimeConfigProvider> <CognitoAuth> <RouterProvider router={router} /> </CognitoAuth> </RuntimeConfigProvider> </I18nProvider> </React.StrictMode>, );
通过 AST 转换在 main.tsx
文件中添加了 RuntimeConfigProvider
和 CognitoAuth
组件。这使得 CognitoAuth
组件可以通过获取包含 Cognito 连接配置的 runtime-config.json
来与 Amazon Cognito 进行身份验证。
游戏 UI:连接游戏 API
Section titled “游戏 UI:连接游戏 API”现在配置游戏 UI 连接之前创建的游戏 API:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)
在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - api-connection
- 填写必需参数
- sourceProject: @dungeon-adventure/game-ui
- targetProject: @dungeon-adventure/game-api
- 点击
Generate
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
您还可以执行试运行以查看哪些文件会被更改
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
您应该看到文件树中出现了新文件或变更。
UI -> tRPC api-connection 更新文件
以下是 api-connection
生成器生成/更新的文件列表。我们将重点查看文件树中高亮的关键文件:
文件夹packages/
文件夹game-ui/
文件夹src/
文件夹components/
- GameApiClientProvider.tsx 设置 GameAPI 客户端
文件夹hooks/
- useGameApi.tsx 调用 GameApi 的钩子
- main.tsx 注入 trpc 客户端提供者
- package.json
import { GameApiTRCPContext } from '../components/GameApiClientProvider';
export const useGameApi = GameApiTRCPContext.useTRPC;
该钩子使用 tRPC 最新的 React Query 集成,允许用户直接与 @tanstack/react-query
交互而无需额外抽象层。有关如何调用 tRPC API 的示例,请参考 使用 tRPC 钩子指南。
import GameApiClientProvider from './components/GameApiClientProvider';import QueryClientProvider from './components/QueryClientProvider';import CognitoAuth from './components/CognitoAuth';import RuntimeConfigProvider from './components/RuntimeConfig';import React from 'react';import { createRoot } from 'react-dom/client';import { I18nProvider } from '@cloudscape-design/components/i18n';import messages from '@cloudscape-design/components/i18n/messages/all.en';import { RouterProvider, createRouter } from '@tanstack/react-router';import { routeTree } from './routeTree.gen';import '@cloudscape-design/global-styles/index.css';const router = createRouter({ routeTree });// 为类型安全注册路由实例declare module '@tanstack/react-router' { interface Register { router: typeof router; }}const root = document.getElementById('root');root && createRoot(root).render( <React.StrictMode> <I18nProvider locale="en" messages={[messages]}> <RuntimeConfigProvider> <CognitoAuth> <QueryClientProvider> <GameApiClientProvider> <RouterProvider router={router} /> </GameApiClientProvider> </QueryClientProvider> </CognitoAuth> </RuntimeConfigProvider> </I18nProvider> </React.StrictMode>, );
通过 AST 转换更新了 main.tsx
文件以注入 tRPC 提供者。
游戏 UI:基础设施
Section titled “游戏 UI:基础设施”最后需要创建 CDK 基础设施子项目。通过以下步骤创建:
- 安装 Nx Console VSCode Plugin 如果您尚未安装
- 在VSCode中打开Nx控制台
- 点击
Generate (UI)
在"Common Nx Commands"部分 - 搜索
@aws/nx-plugin - ts#infra
- 填写必需参数
- name: infra
- 点击
Generate
pnpm nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
yarn nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
npx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
bunx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
您还可以执行试运行以查看哪些文件会被更改
pnpm nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
您应该看到文件树中出现了新文件或变更。
ts#infra 更新文件
以下是 ts#infra
生成器生成/更新的文件列表。我们将重点查看文件树中高亮的关键文件:
文件夹packages/
文件夹common/
文件夹constructs/
文件夹src/
文件夹core/
- checkov.ts
- index.ts
文件夹infra
文件夹src/
文件夹stages/
- application-stage.ts 定义 CDK 堆栈
文件夹stacks/
- application-stack.ts 定义 CDK 资源
- index.ts
- main.ts 定义所有阶段的入口点
- cdk.json
- project.json
- …
- package.json
- tsconfig.json 添加引用
- tsconfig.base.json 添加别名
import { ApplicationStage } from './stacks/application-stage.js';import { App } from ':dungeon-adventure/common-constructs';
const app = new App();
// 部署自己的沙盒环境(假设已配置 CLI 凭证)new ApplicationStage(app, 'dungeon-adventure-infra-sandbox', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, },});
app.synth();
这是 CDK 应用的入口点。
import * as cdk from 'aws-cdk-lib';import { Construct } from 'constructs';
export class ApplicationStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props);
// 在此定义堆栈代码 }}
这是实例化 CDK 构造体以构建地牢冒险游戏的位置。
更新基础设施
Section titled “更新基础设施”更新 packages/infra/src/stacks/application-stack.ts
以实例化部分已生成的构造体:
import { GameApi, GameUI, InventoryMcpServer, RuntimeConfig, 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 { userPool, userPoolClient } = userIdentity;
const mcpServer = new InventoryMcpServer(this, 'InventoryMcpServer');
// Use Cognito for user authentication with the agent const storyAgent = new StoryAgent(this, 'StoryAgent', { authorizerConfiguration: { customJwtAuthorizer: { discoveryUrl: `https://cognito-idp.${Stack.of(userPool).region}.amazonaws.com/${userPool.userPoolId}/.well-known/openid-configuration`, allowedAudience: [userPoolClient.userPoolClientId], }, }, environment: { INVENTORY_MCP_ARN: mcpServer.agentCoreRuntime.arn, }, }); // Add the Story Agent ARN to runtime-config.json so it can be referenced by the website RuntimeConfig.ensure(this).config.agentArn = storyAgent.agentCoreRuntime.arn;
new CfnOutput(this, 'StoryAgentArn', { value: storyAgent.agentCoreRuntime.arn, }); new CfnOutput(this, 'InventoryMcpArn', { value: mcpServer.agentCoreRuntime.arn, });
// Grant the agent permissions to invoke our mcp server mcpServer.agentCoreRuntime.grantInvoke(storyAgent.agentCoreRuntime);
// Grant the authenticated role access to invoke the api gameApi.grantInvokeAccess(userIdentity.identityPool.authenticatedRole);
// Ensure this is instantiated last so our runtime-config.json can be automatically configured new GameUI(this, 'GameUI'); }}
import { Stack, StackProps } from 'aws-cdk-lib';import { GameApi, GameUI, InventoryMcpServer, RuntimeConfig, 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 { userPool, userPoolClient } = userIdentity;
const mcpServer = new InventoryMcpServer(this, 'InventoryMcpServer');
// Use Cognito for user authentication with the agent const storyAgent = new StoryAgent(this, 'StoryAgent', { authorizerConfiguration: { customJwtAuthorizer: { discoveryUrl: `https://cognito-idp.${Stack.of(userPool).region}.amazonaws.com/${userPool.userPoolId}/.well-known/openid-configuration`, allowedAudience: [userPoolClient.userPoolClientId], }, }, environment: { INVENTORY_MCP_ARN: mcpServer.agentCoreRuntime.arn, }, }); // Add the Story Agent ARN to runtime-config.json so it can be referenced by the website RuntimeConfig.ensure(this).config.agentArn = storyAgent.agentCoreRuntime.arn;
new CfnOutput(this, 'StoryAgentArn', { value: storyAgent.agentCoreRuntime.arn, }); new CfnOutput(this, 'InventoryMcpArn', { value: mcpServer.agentCoreRuntime.arn, });
// Grant the agent permissions to invoke our mcp server mcpServer.agentCoreRuntime.grantInvoke(storyAgent.agentCoreRuntime);
// Grant the authenticated role access to invoke the api gameApi.grantInvokeAccess(userIdentity.identityPool.authenticatedRole);
// Ensure this is instantiated last so our runtime-config.json can be automatically configured new GameUI(this, 'GameUI'); }}
注意这里我们为 Game API 提供了默认集成。默认情况下,API 中的每个操作都映射到独立的 lambda 函数处理。
Nx 命令
单目标 vs 多目标
Section titled “单目标 vs 多目标”run-many
命令将在多个列出的子项目上运行目标(--all
将针对所有项目)。确保依赖项按正确顺序执行。
也可以通过直接在项目上运行目标来触发构建(或任何其他任务)。例如,要构建 @dungeon-adventure/infra
项目,可运行:
pnpm nx run @dungeon-adventure/infra:build
yarn nx run @dungeon-adventure/infra:build
npx nx run @dungeon-adventure/infra:build
bunx nx run @dungeon-adventure/infra:build
也可以省略作用域,使用 Nx 简写语法:
pnpm nx build infra
yarn nx build infra
npx nx build infra
bunx nx build infra
可视化依赖关系
Section titled “可视化依赖关系”可通过以下命令可视化依赖关系:
pnpm nx graph
yarn nx graph
npx nx graph
bunx nx graph

Nx 依赖缓存以重用先前构建产物加速开发。需要配置才能正常工作,有时可能需要不使用缓存进行构建。为此,只需在命令后添加 --skip-nx-cache
参数。例如:
pnpm nx run @dungeon-adventure/infra:build --skip-nx-cache
yarn nx run @dungeon-adventure/infra:build --skip-nx-cache
npx nx run @dungeon-adventure/infra:build --skip-nx-cache
bunx nx run @dungeon-adventure/infra:build --skip-nx-cache
如需清除缓存(存储在 .nx
文件夹),可运行:
pnpm nx reset
yarn nx reset
npx nx reset
bunx nx reset
pnpm nx run-many --target build --all
yarn nx run-many --target build --all
npx nx run-many --target build --all
bunx nx run-many --target build --all
您应该看到以下提示:
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 changes
该消息表示 NX 检测到可自动更新的文件。此处指未设置项目引用的 tsconfig.json
文件。选择 Yes, sync the changes and run the tasks 继续。您会注意到所有 IDE 导入错误自动解决,因为同步生成器会自动添加缺失的 TypeScript 引用!
所有构建产物现在位于 monorepo 根目录的 dist/
文件夹中。这是使用 @aws/nx-plugin
生成项目的标准做法,避免生成文件污染文件树。如需清理文件,只需删除 dist/
文件夹即可。
恭喜!您已创建开始实现地牢冒险游戏核心所需的所有子项目。🎉🎉🎉