@ chamvi/ app (中文(简体) )
Boots chamvi代理作为应用程序:配置,存储,引擎,通道和HTTP作为Orkos模块.
代理仓库是 createApp() 默认导出;
chamvi start | run-job | migrate (单位:千美元)校对:soup运行它.
目录
安装
npm install @chamvi/app @chamvi/cli ai @ai-sdk/anthropic typeorm pg
仅限无害环境管理,节点.js >=26.0.0。 。 。 . ai, (中文(简体) ), typeorm 财务报告和审定财务报表 pg 属于对等依赖; 提供者软件包(@ai-sdk/anthropic, (中文(简体) ), @ai-sdk/openai, (中文(简体) ), @ai-sdk/openai-compatible, (中文(简体) ), @ai-sdk/google, (中文(简体) ), @ai-sdk/mistral, (中文(简体) ), @ai-sdk/xai)是可选的同位素:安装您使用的同位素。 带有它们配置的软件包的可选模块船(npm install @chamvi/github 页:1 @chamvi/github/module),其中的同级 @chamvi/app。 。 。 .
主要进项再出口 @chamvi/core 和共同部分 @chamvi/http (单位:千美元)agentPermission, (中文(简体) ), Permission,则 Authenticator, (中文(简体) ), Principal, (中文(简体) ), HttpRoute* 财务报告和审定财务报表 StaticToken ),所以代理寄存器会导入一个包.
使用量
src/index.ts 将应用程序导出为默认导出 :
import {
consoleChannelModule,
createApp,
cron,
defineModelAgent,
http,
} from '@chamvi/app';
const triage = defineModelAgent({
name: 'triage',
// no model: the app's default below
instructions: 'Triage the reported issue: label it and summarize the next step.',
channels: [
// POST /agents/triage/runs, the UI and MCP
http.on(),
// every 30 minutes, Berlin time
cron.on('*/30 * * * *', { timezone: 'Europe/Berlin' }),
],
});
// chamvi start | run-job | migrate load this default export
export default createApp({
// the default model; CHAMVI_MODEL overrides it, ANTHROPIC_API_KEY configures the provider
model: 'anthropic/claude-sonnet-5-5',
agents: [triage],
// tools write to it with ctx.channels.findOneOrFail('console').emit(...)
modules: [consoleChannelModule()],
});
createApp() 是同步和副作用自由的:没有env读取,没有I/O. 生命周期方法中的一切发生,CLI称之为:
| 方法 | 国 际 | 对 |
|---|---|---|
start({ env?, dev? }) | chamvi start | 设置每个模块, 一旦 HTTP 服务器收听后解决 |
stop() | 首个签名或签名 | 被纪念; 中止一个仍然设置的开始, 撕下每个模块 |
runJob({ agent, subscription?, env? }) | chamvi run-job <agent> [subscription] | 一次性开出一个 cron 订阅并等待运行( 没有频道启动, 没有 HTTP ); { runId?, status, exitCode } |
migrate({ env? }) | chamvi migrate | 迁移商店( e)DATABASE_URL 并关闭 |
check({ env?, dev? }) | chamvi start --check | 启动路径直到其启动步骤( 配置、 存储连接、 模型设置、 每个模块、 代理文件夹的负载和检查) , 然后是拆解 ; 没有写到商店, 没有开始, 没有运行任何 engineCreated 财务报告和审定财务报表 drained 运行); { issues } (代理文件夹的负载问题) |
env 默认为 process.env应用程序运行一个生命周期(check 计算为一; 拒绝的第二个 app_in_use。 。 。 . isApp(value) 识别一个应用, 也是另一个安装的软件包副本 .
agents 在通过密码的代理人之外,还可以取出光包规格: createApp({ agents: [triage, '@acme/review-agent'] }) 装入包的默认导出( 代理、 代理阵列、 同步或同步工厂返回其中之一) , 启动时使用 @chamvi/*, (中文(简体) ), ai 财务报告和审定财务报表 @ai-sdk/* 解决了应用程序的副本. chamvi.json agents 财务报告和审定财务报表 CHAMVI_AGENTS (已分出) 从部署中添加包. 软件包代理版本是其版本(<name>@<version>;在dev加一个内容散列中,所以一个版本的突起会取消它一次未解决的运行. 代理通过代码赢得一个名称,然后包,然后文件夹. 见 代理软件包。 。 。 .
探员 instructions 还接受命令只读的原生系统/用户/辅助消息. AgentInstructions, (中文(简体) ), SystemModelMessage, (中文(简体) ), UserModelMessage, (中文(简体) ), AssistantModelMessage 和本地内容类型导出 @chamvi/core 并用这包再出口。 字符串仍然兼容。 见 键入指令示例 用于特定角色内容,提供者选项和瞬间检查站行为.
核定的当地技能
使用 createApp({ skills: [common] }) 对每一个代理的添加剂默认 通过代码和每一个文件夹代理。 每个 defineModelAgent({ skills: [...] }) 增加范围外加. 引擎附加元数据并提供 load_skill 自动; 用户工具名称 load_skill 拒绝。 此示例重新使用 triage 上面的用户.
import { createApp } from '@chamvi/app';
import { gitSkills } from '@chamvi/skills';
const common = gitSkills({
source: 'https://github.com/tada5hi/skills.git',
revision: '0123456789abcdef0123456789abcdef01234567', // replace with reviewed commit
directory: '/tmp/chamvi-skills/common',
approved: [{
name: 'ponytail',
version: '1.0.0',
hash: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', // reviewed digest
}],
});
export default createApp({
model: 'anthropic/claude-sonnet-5-5',
agents: [triage],
skills: [common],
});
在独立审查所有捆绑文本后, 替换示例性标点 . gitSkills 是一个纯粹的工厂 从技能主条目。 启动准备全局和代理商来源在代码之前通过 engineCreated;文件夹-代理外接器在之后,在频道,HTTP或模型工作之前准备. 校验失败中止启动; 失败的技巧热重装保留了旧代理, 记录代理问题, 并重试 。 文件夹编辑重置延迟; 成功恢复清除了问题 . app.check() 验证静态信头,不装入提供者或安装源,因此远程验证被推迟. 移动跳过准备.
对于已经部署的文件, loadSkillBundle(descriptor) 返回已准备好的快照. hashSkillBundle(directory) 是一种单独的审查援助,绝不批准。 手册 loadSkillBundles(approved) 仍然支持目录: 附加其指示并明确编写工具, 检查是否 load_skill 碰撞。 技能类型、目录和适配器defineSkill, (中文(简体) ), defineSkillSource也属于 @chamvi/skills,没有兼容应用程序导出。 工厂已经运行时间;不需要模块登记。 核心/应用 SkillInput 合同和不依赖技能.
运行时需要 Git 源 @chamvi/skills, Node QQ26.0.0, Git (从基点Node 26图像中退出),可写出管理的目录/tmp,源证书/网络和启动探测时间用于克隆和验证. 停止加入清理,并可能等待一个不可继承的克隆人. 预建快照只需要可读的运行时间资产 .
快照是不可改变的 所保存的负载结果保留了原始的检查点字节;在被指针升级后等待/未来的负载使用当前已核实的快照. 全局更改不会改变文件夹散列:当混合版本披露不可接受时,在升级前完成或取消未解决的运行. 有轨电车代理外件编辑使用已存在的修订版取消. 技能不授予工具,脚本执行,子代理或权限. 见 核定技能 编辑 技能 API。 。 。 .
环境
每个变量每个生命周期读取一次. 空字符串计数为未设置,整数仅为数字(每个 *_MS 变量最多 TIMER_MAX_MS,节点的时间限制),布尔 true, (中文(简体) ), false, (中文(简体) ), 1 或者说 0。一个错误的值抛出 InvalidAgentDefinitionError 命名变量,绝不重复其值.
| 变量 | 阅读者 | 数值 | 默认 |
|---|---|---|---|
PORT | 网页 | 单位 1.65535 | 3000 |
HOST | 网页 | 字符串 | 0.0.0.0 |
CHAMVI_BASE_PATH | 网页 | 空或无 ^(?:\/[a-z0-9._~-]+)+$ | '' (卫生状况 /healthz,UI 时 /ui(中文(简体) ) |
CHAMVI_PUBLIC_URL | ii,频道 | 绝对 URL | 未设置; 设置为批准链接( u)<url><basePath>/ui/approvals?run=<runId>&approval=<approvalId>(中文(简体) ) |
CHAMVI_LOG_LEVEL | 配置 | error, (中文(简体) ), warn, (中文(简体) ), info, (中文(简体) ), debug | info |
DATABASE_URL | 存储 | postgres://... | 未设定 : createMemoryStore() (外边警告) --dev; 所需时间 migrate 和任何多层或多层部署 |
CHAMVI_MIGRATE | 存储 | 嘘 | true (单位:千美元)start 财务报告和审定财务报表 run-job (移徙,咨询封闭) |
CHAMVI_PURGE_INTERVAL_MS | 存储 | 内, 0 禁用 | 3600000 (用于可清除能力储存库) |
CHAMVI_MAX_CONCURRENT_RUNS | 引擎 | 单位 1 | 4 |
CHAMVI_SCHEDULER | 调度器 | 嘘 | true· ; false 启动无进程 cron( CronJob 模式); cron.on() 订阅仍在验证) |
CHAMVI_READINESS_DELAY_MS | 关闭 http | 单位 | 0 |
CHAMVI_DRAIN_TIMEOUT_MS | 关闭 http | 单位 | 30000 (单位:千美元)--dev 页:1 |
CHAMVI_CLOSE_TIMEOUT_MS | 关闭 http | 单位 | 5000 |
CHAMVI_TOKENS | 静态符号 | 贾森 [{ "value", "id", "permissions": [] }], (中文(简体) ), value 至少16个字符和唯一 | 未设置( n)--dev: 一个随机 * 符号,警告;当没有配置其他认证器时,需要 |
CHAMVI_MODEL | 引擎 | '<provider>/<model id>' | 配置文件的 model,则 createApp({ model }); 当代理人没有自己的型号时需要 |
CHAMVI_CONFIG_FILE | 引擎 | jSON 文件的路径 | 未设定 : ./chamvi.json 当它存在时 |
CHAMVI_PROVIDER_<NAME>_TYPE, (中文(简体) ), _API_KEY, (中文(简体) ), _BASE_URL, (中文(简体) ), _HEADER_<HEADER> | 引擎 | 类型、 秘密、 http(s) URL、 秘密 | 未设定; 提供方的配置文件和代码各胜一负 <name> (见 模型(中文(简体) ) |
ANTHROPIC_API_KEY, (中文(简体) ), OPENAI_API_KEY, (中文(简体) ), GOOGLE_GENERATIVE_AI_API_KEY, (中文(简体) ), MISTRAL_API_KEY, (中文(简体) ), XAI_API_KEY | 引擎 | 字符串 | 未设置; 每个创建其类型的隐含提供者( q)ANTHROPIC_BASE_URL, (中文(简体) ), OPENAI_BASE_URL 设置其基础 URL),除非源声明名称 |
GITHUB_APP_ID, (中文(简体) ), GITHUB_APP_PRIVATE_KEY (中文(简体) ) \n 允许, GITHUB_WEBHOOK_SECRET (为轮换而分开) | githubModule | 字符串 | 需要没有选项 |
GITHUB_INSTALLATION_ID, (中文(简体) ), GITHUB_APP_SLUG, (中文(简体) ), GITHUB_API_URL | githubModule | 英寸、 斜体、 https URL | 未设定 |
OPENCLAW_BASE_URL, (中文(简体) ), OPENCLAW_HOOKS_TOKEN, (中文(简体) ), OPENCLAW_CHANNEL, (中文(简体) ), OPENCLAW_TO | openclawModule | URL、字符串、字符串、字符串 | 需要没有选项 |
OPENCLAW_HOOKS_PATH, (中文(简体) ), OPENCLAW_AGENT_ID, (中文(简体) ), OPENCLAW_ACCOUNT_ID | openclawModule | 字符串 | /hooks,未设置,未设置 |
AUTHUP_URL, (中文(简体) ), AUTHUP_CLIENT_ID, (中文(简体) ), AUTHUP_CLIENT_SECRET | authupModule | URL、字符串、字符串 | 所需无 verifier |
CHAMVI_UI_AUTHUP_CLIENT_ID, (中文(简体) ), CHAMVI_UI_AUTHUP_REALM_ID | uiModule | 字符串 | 未设定( 仅输入登录); 认证登录需要 AUTHUP_URL |
CHAMVI_SANDBOX_PROVIDER | sandboxModule | local, (中文(简体) ), docker, (中文(简体) ), kubernetes | 页:1 provider 选项,否则 local |
CHAMVI_SANDBOX_NAMESPACE | sandboxModule | 字符串 | 所需经费 kubernetes |
CHAMVI_SANDBOX_IMAGE | sandboxModule | 图像参考 | ghcr.io/chamvi/sandbox-dev:trixie |
CHAMVI_DEPLOYMENT | sandboxModule | 标签值 | 页:1 deployment 选项,否则 default |
内置模块读取生命周期 env (单位:千美元)start({ env })) (中文(简体) ). 可选模块工厂读作 options.env ?? process.env 当他们被召唤的时候,即输入条目时,绝不 start({ env });测试通过它们配置 env 选项 :
githubModule({ env: { GITHUB_APP_ID: '1', GITHUB_APP_PRIVATE_KEY: pem, GITHUB_WEBHOOK_SECRET: 'secret' } });
HTTP 摄取和执行限制
CHAMVI_MAX_CONCURRENT_RUNS 限制每个引擎实例( 默认) 4) (中文(简体) ). 设定 concurrencyMaxRuns 代理人和/或其代理人 channel.on(...) 订阅在共享商店的复制品上添加嵌入式天花板. 正安全整数盖运行; false 或疏漏不会增加该级别的上限,也永远不会绕过母上限。 等待批准释放出数字容量,但保留货币键排除。 多余的运行会持续排队, 没有存储绑定 .
单独通过配置挂载的频道 HTTP 策略 httpModule({ channelRateLimits: { github: { max: 3000, windowMs: 60000 } } }),或使用 github: false 只禁用该频道的请求限制器。 开放渠道共用一般REST桶;签名和身体限制仍然有效. trustProxy 必须只识别已知的入侵源;入侵配置不会自动设定。 这些是TypeScript选项,而不是新的环境变量. A 类 202 承认存储接受,而不是执行完成。 GitHub 不自动重播失败的 Webhook 交付, 所以监视和调节它们 。 见 GitHub 摄入量 财务报告和审定财务报表 货币。 。 。 .
模块
模块为 Orkos IModules在一个Eldin容器。 内置的总是在那里;列出一个有内置名称的模块(ModuleName(单位:千美元) modules 替换它。 一个名字的两个模块,一个内置的,扔 InvalidAgentDefinitionError。 。 。 .
| 模块 | 名称 | 条目 | 对 |
|---|---|---|---|
configModule() | chamvi:config | @chamvi/app | 读取配置, 注册 CONFIG, (中文(简体) ), CLOCK, (中文(简体) ), LOGGER |
storeModule({ url?, store?, dataSource? }) | chamvi:store | @chamvi/app | createDatabaseStore() 结束 DATABASE_URL (移徙),其他 createMemoryStore(); 登记册 AppToken.STORE 可清除的角色 |
staticTokensModule({ tokens? }) | chamvi:static-tokens | @chamvi/app | 静键验证器从 CHAMVI_TOKENS |
engineModule({ owner?, claimTtlMs? }) | chamvi:engine | @chamvi/app | 检查模型字符串,构建 Engine 与每个贡献的频道(CHANNELS)并开始它(它开始频道) |
schedulerModule({ owner?, lockTtlMs? }) | chamvi:scheduler | @chamvi/app | 页:1 cron 频道 (cronChannel() 联 合 国 @chamvi/channel-cron;在启动模式中,只用 CHAMVI_SCHEDULER(中文(简体) ) |
httpModule() | chamvi:http | @chamvi/app | 页:1 http, (中文(简体) ), mcp 财务报告和审定财务报表 webhook 频道(用应用程序的认证器认证), createHttpApp() 以一个 in 时间 /channels/<name>优雅的关闭,听最后 |
consoleChannelModule({ name?, write? }) | chamvi:console (单位:千美元)chamvi:console:<name> 名称) | @chamvi/app | consoleChannel() (名称) console,仅退出) |
githubModule() | chamvi:github | @chamvi/github/module | gitHub App 及其频道(github: 签到 /channels/github评论; injectGithubContext(ctx) 财务报告和审定财务报表 ctx.channels.findOne('github') 用于工具 |
sandboxModule({ provider?, ... }) | chamvi:sandbox | @chamvi/sandbox/module | 运行范围的工作空间管理器( e).manager, (中文(简体) ), injectSandboxContext(ctx)),发动机排出后关闭 |
openclawModule() | chamvi:channel:<name> | @chamvi/channel-openclaw/module | openclawChannel() (名称) openclaw,仅出, ctx.channels.findOne('openclaw') |
uiModule() | chamvi:ui | @chamvi/ui/module | 仪表板在 <basePath>/ui |
authupModule() | chamvi:authup | @chamvi/app/authup | 在静态符号之后的认证认证符 |
oidcModule(options) | chamvi:oidc | @chamvi/app/authup | 页:1 oidc() 认证器, 认证后( 无嵌入形式) |
生命周期
start 按此顺序设置模块, 并最后聆听, 所以 /readyz 只有在一切运行之后,绿色才会变:
- 配置
- 存储(和迁移)
- 每个
onReady(包含用户模块):引擎(每个贡献的信道)和HTTP应用程序被构建,还没有运行(一个配置错误开始什么也没有) engine.start()恢复,然后每个频道的start()(他们之间的折叠时间表)- 清洗计时器
serve()打开HOST数字 :PORT
stop() (同样在开始阶段 还在设置, 然后从来不听):
/readyz回答 503,然后CHAMVI_READINESS_DELAY_MS- 清理计时器停止
- 应用程序的更近( SSE 流, MCP 等)
server.close()engine.drain(): 每个频道的stop()首先,然后直到CHAMVI_DRAIN_TIMEOUT_MS运行- 最多
CHAMVI_CLOSE_TIMEOUT_MS结束,那么server.close(true) - 工作空间管理器关闭( 与
sandboxModule(中文(简体) ) - 该商店的数据源被销毁
stop() 拒绝方式 app_stop_failed ; CLI 然后退出 1.
写入模块
一个模块贡献于 setup 并消耗在 onReady在每一个模块之后 setup 运行 :
import type { IContainer, IModule } from '@chamvi/app';
import { AppToken, ModuleName } from '@chamvi/app';
export function auditModule(): IModule {
return {
name: 'audit',
// onReady resolves ENGINE below, so the engine module comes first
dependencies: [ModuleName.ENGINE],
async setup(container: IContainer) {
container.registerMany(AppToken.HTTP_ROUTES, {
useValue: {
method: 'GET',
path: '/audit',
handler: async () => ({ status: 200, body: { ok: true } }),
},
});
},
async onReady(container: IContainer) {
// migrate builds config and store only: no ENGINE to resolve
if (container.resolve(AppToken.CONTEXT).mode === 'migrate') {
return;
}
const logger = container.resolve(AppToken.LOGGER);
container.resolve(AppToken.ENGINE).on('run.updated', ({ run }) => {
logger.info('run updated', { run: run.id, status: run.status });
});
},
};
}
模块所需的一切 @chamvi/app数字 : AppToken, (中文(简体) ), ModuleName, (中文(简体) ), ILifecycleHook, (中文(简体) ), AppContext,"巢穴"读取器(:envString, (中文(简体) ), envUrl,(......), approvalUrlOf 和奥克语 IModule 埃尔丁 IContainer 类型.
一个构建实例工具的模块在引擎上提供 engineCreated,通过软件包的上下文键;工具读取 inject(ctx, key),因此不需要关闭模块:
import type { IContainer, IModule } from '@chamvi/app';
import { AppToken, defineContextKey, defineTool, inject } from '@chamvi/app';
type Audit = { write(entry: string): void };
const AUDIT = defineContextKey<Audit>('audit');
export function auditLogModule(audit: Audit): IModule {
return {
name: 'audit-log',
async setup(container: IContainer) {
container.registerMany(AppToken.LIFECYCLE_HOOKS, {
useValue: { name: 'audit-log', engineCreated: (engine) => engine.provide(AUDIT, audit) },
});
},
};
}
// in an agent: tools: (ctx) => { const audit = inject(ctx, AUDIT); return { ... }; }
注入到 tools 当模块缺失时, 厂家在任何模式调用之前都会失败运行( namecontext_not_provided; 内 execute 这是模型所看到的工具错误。 模块 spec 运行没有端口的真实模块生命周期 @chamvi/app/testing。这个带子只设置了它给定的模块,所以一个其钩子需要引擎的模块列出了它所基于的内置:
import { configModule, engineModule, storeModule } from '@chamvi/app';
import { createModuleHarness } from '@chamvi/app/testing';
const harness = await createModuleHarness({
env: {},
agents: [agent],
modules: [configModule(), storeModule(), engineModule(), auditLogModule(audit)],
});
// harness.container, harness.context, harness.reported (what context.report() got)
await harness.stop();
- 键 (单位:千美元)
AppToken:CONTEXT, (中文(简体) ),CONFIG, (中文(简体) ),LOGGER, (中文(简体) ),CLOCK, (中文(简体) ),STORE, (中文(简体) ),DATA_SOURCE, (中文(简体) ),ENGINE, (中文(简体) ),MODELS(引擎模块的模型解析器,注册于setup在开始和工作模式以及支票中),SHUTDOWN财务报告和审定财务报表SERVER持有一个值;HTTP_ROUTES, (中文(简体) ),AUTHENTICATORS(单位:千美元){ name, order, authenticator }最低点order首先,AUTHENTICATOR_ORDER, (中文(简体) ),CHANNELS(每个IChannel; 唯一名称, 具有in挂载于/channels/<name>, (中文(简体) ),LIFECYCLE_HOOKS财务报告和审定财务报表AGENT_CHECKS为收藏(registerMany(token, { useValue })内setup, (中文(简体) ),resolveAll内onReady) (中文(简体) ). - 顺序由于每个消费者都参与其中
onReady模块ENGINE或者说SERVER内onReady列表中该模块dependencies; 应用程序不注册的依赖性, 周期或版本不匹配是一个InvalidAgentDefinitionError在任何模块设置之前. - 模式数字 :
CONTEXT.mode实值start(每件事物),job(配置、储存、引擎)或migrate因此模块解析ENGINE内onReady提前返回migrate。 。 。 . - 钩子 (单位:千美元)
LIFECYCLE_HOOKS, (中文(简体) ),ILifecycleHook:engineCreated(engine)在此之前engine.start(), (中文(简体) ),started()之后engine.start()(频道启动),在服务器听之前,stopping()首先在关闭, 在排水管道停止之前,drained()在发动机排出后, 在商店关闭之前. - 失败: orkos从不撕下一个模块
setup抛出,所以setup它会先关闭它打开的东西 一个模块onReady扔下来了,所以teardown容忍状态onReady从来没有建造. - 停止中数字 :
CONTEXT.signal中止一次stop()称为;throwIfStopped(context)在漫长的一步中, 保持一个停止的开始继续.context.report(error)日志和期间stop()它会拒绝.
模型
经纪人的 model 是可选的:如果没有一个,它会接受应用程序的默认。 模型字符串 '<provider>/<model id>' (第一次分开) /这样 router/cc/claude-opus-4-6 是提供者 router通过应用程序的提供者解决,从不通过Vercel AI Gateway或AI SDK的全球提供者解决; a LanguageModel 对象按原样使用.
CHAMVI_MODEL=anthropic/claude-sonnet-5-5 # the default model
ANTHROPIC_API_KEY=... # the implicit provider "anthropic"
CHAMVI_PROVIDER_ROUTER_TYPE=openai-compatible # a named provider: 9router, OmniRoute, Ollama, vLLM, LiteLLM, OpenRouter
CHAMVI_PROVIDER_ROUTER_BASE_URL=http://9router:20128/v1
CHAMVI_PROVIDER_ROUTER_API_KEY=...
相同的设置来自 createApp({ model, providers }) (单位:千美元) chamvi.json 文件( 编号CHAMVI_CONFIG_FILE); env胜过文件,文件胜过代码,每个提供者和字段,以及 createApp({ models }) (AI SDK 提供者对象由 id) 胜过所有它的 ids 。 没有模型和默认的代理, 或指定未配置提供者的字符串, 失败 start 财务报告和审定财务报表 runJob 与 InvalidAgentDefinitionError 命名要设置的变量( CLI 退出 2); 文件夹代理成为负载问题 . chamvi start --check (单位:千美元)app.check())对真实的配置和存储进行相同的检查,不开始任何操作或写入商店.
见 选择一个模型 对于提供者,秘密,网关,配置文件和优先规则.
许可证
原载 Apache 许可证 2.0。 。 。 .