FEATURED · 精选文章

oh-my-pi 中的 Gemini Manifest Extensions(gemini-extension.json)发现与解析机制全解

发布时间 / 2026/9/11 6:15:30
来源 / 创域科博编辑部
栏目 / 资讯中心
oh-my-pi 中的 Gemini Manifest Extensions(gemini-extension.json)发现与解析机制全解 oh-my-pi 中的 Gemini Manifest Extensionsgemini-extension.json发现与解析机制全解【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi导读本文深入解析 oh-my-pi 的coding-agent如何发现并解析 Gemini CLI 风格扩展清单gemini-extension.json将其物化为extensionscapability 条目。通过阅读本文你将掌握~/.gemini/extensions与cwd/.gemini/extensions两个固定扫描根的目录扫描规则、清单 JSON 的宽松解析语义、名称归一化与_source溯源元数据的生成方式、错误处理与告警边界、以及与nativeprovider 之间的跨 provider 优先级去重行为并理解 Gemini 清单元数据与 TS/JS 扩展模块extension-module之间的边界。文末还给出了可直接落地的示例目录布局与排查建议。本文不涉及 TypeScript/JavaScript 扩展模块的加载机制extensions/*.ts、index.ts、package.json中的omp.extensions相关内容见 Extension Loading。一、实现文件与整体架构定位Gemini manifest extensions 的发现与解析由以下文件共同完成Gemini provider 发现逻辑注册geminiproviderid: gemini优先级60负责扩展清单的扫描与解析builtinnativeprovider注册nativeproviderid: native优先级100同样实现extensionscapability发现工具函数getUserPath()、getProjectPath()、createSourceMeta()等路径解析与溯源工具extensions capability 定义定义ExtensionManifest与Extension类型extension-module capability 定义定义扩展模块条目类型capability 注册中心registerProvider()、loadCapability()、provider 启用/禁用状态管理扩展模块加载器TS/JS 扩展模块的实际装载入口Gemini 清单不直接触发该加载器从 capability 类型定义 可以看到整个配置发现系统采用了能力倒置inverted control架构调用方无需关心.gemini、.claude、.codex等具体目录只需调用load(extensions)即可获得统一的能力条目数组具体路径由各 provider 负责。1.1 Gemini provider 的注册信息在 gemini.ts 中extensionscapability 的 provider 注册如下registerProvider(extensionCapability.id, { id: PROVIDER_ID, // gemini displayName: DISPLAY_NAME, // Gemini CLI description: Load extensions from ~/.gemini/extensions/ and .gemini/extensions/, priority: PRIORITY, // 60 load: loadExtensions, });注意Gemini provider 除了extensions之外还注册了mcps、context-files、system-prompt、extension-modules、settings等多项 capability见 gemini.ts说明它统一负责从 Gemini CLI 配置目录读取各类配置扩展清单只是其中一环。二、发现范围两个固定扫描根与 cwd-only 规则Gemini provider 的loadExtensions()扫描两个固定根目录见 gemini.ts用户级~/.gemini/extensions通过getUserPath(ctx, gemini, extensions)解析项目级cwd/.gemini/extensions通过getProjectPath(ctx, gemini, extensions)解析路径解析直接依赖LoadContext中的ctx.home与ctx.cwd。两个关键实现事实项目查找是 cwd-only 的getProjectPath()直接执行path.join(ctx.cwd, .gemini, subpath)见 helpers.ts不会像 native provider 那样向上遍历祖先目录。也就是说只有当前工作目录下的.gemini/extensions会被发现父目录中的同名配置不会生效。用户级路径受 opt-in 门控getUserPath()内部调用isUserSourceEnabled(gemini, ctx)见 helpers.ts。由于gemini属于 FOREIGN_USER_PROVIDERS外部工具的用户级配置默认不启用~/.gemini/extensions只有在以下任一条件满足时才会被扫描调用方显式指定了providers: [gemini]用户在设置中启用了enabledProviders包括gemini、*或all以includeDisabled方式加载用于 dashboard 展示并允许开启。相比之下项目级cwd/.gemini/extensions不受此门控影响始终参与扫描。2.1 SOURCE_PATHS 中的 Gemini 路径定义在 helpers.ts 中Gemini 的路径根定义如下gemini: { userBase: .gemini, userAgent: .gemini, projectDir: .gemini, },即用户级与项目级都使用.gemini目录名这也与 Gemini CLI 自身的约定保持一致。三、目录扫描规则一层深度不递归对每个根目录loadExtensionsFromDir()见 gemini.ts执行如下流程调用readDirEntries(extensionsDir)读取目录条目仅保留直接子目录entry.isDirectory()对每个子目录name尝试读取恰好一个文件root/name/gemini-extension.json。代码实现const entries await readDirEntries(extensionsDir); const dirEntries entries.filter(entry entry.isDirectory()); const results await Promise.all( dirEntries.map(async entry { const extPath path.join(extensionsDir, entry.name); const manifestPath path.join(extPath, gemini-extension.json); const content await readFile(manifestPath); return { entry, extPath, manifestPath, content }; }), );没有超过一层的递归扫描gemini-extension.json必须直接位于root/name/下子目录中的嵌套结构不会被发现。3.1 隐藏目录dot-prefixed的处理差异这里有一个容易被忽略的细节Gemini 清单发现不会过滤以点开头的目录名。如果.hidden-dir/gemini-extension.json存在它同样会被纳入考虑。这一点与 native provider 形成了鲜明对比在 builtin.ts 中native 的扩展扫描会显式跳过entry.name.startsWith(.)的目录。因此在.gemini/extensions下放置隐藏目录Gemini 会读取而 native针对自己的extensions目录不会。3.2 缺失/不可读文件的静默跳过如果子目录中没有gemini-extension.json或该文件不可读readFile返回空则该目录被静默跳过不产生任何警告。只有一种情况会产生警告文件内容非空但 JSON 解析失败详见第五节。四、清单形态与宽松解析语义4.1 Manifest 类型定义extensionscapability 定义的清单形态如下见 extension.tsexport interface ExtensionManifest { name?: string; description?: string; mcpServers?: Recordstring, OmitMCPServer, name | _source; tools?: unknown[]; context?: unknown; }字段语义字段类型说明namestring可选扩展名缺失时回退为目录名descriptionstring可选扩展描述mcpServers记录可选MCP 服务器配置复用MCPServer类型不含name与_source由发现层补充toolsunknown[]可选工具声明发现阶段不做结构校验contextunknown可选上下文数据发现阶段不做结构校验4.2 宽松的解析门控发现期行为是刻意宽松的见 gemini.tsfor (const { entry, extPath, manifestPath, content } of results) { if (!content) continue; const manifest tryParseJsonExtensionManifest(content); if (!manifest) { warnings.push(Invalid JSON in ${manifestPath}); continue; } items.push({ name: manifest.name ?? entry.name, path: extPath, manifest, level, _source: createSourceMeta(PROVIDER_ID, manifestPath, level), }); }关键语义文件必须非空且tryParseJson()返回真值。因此非法 JSON 以及合法但为假值的 JSON 字面量null、false、0、都会走同一条警告路径。通过该门控之后不再进行任何运行时 schema 校验字段类型/内容均不检查。解析后的值以manifest字段存储在 capability 条目上。也就是说语义有效性并不被强制tryParseJson()的真值性才是警告门控而非ExtensionManifest运行时校验器。一个语义上残缺例如既无mcpServers也无tools但语法合法的 JSON 也能通过。4.3 名称归一化规则Extension.name的赋值规则两分支无字符串类型强制若manifest.name不为null/undefined则使用manifest.name否则回退为扩展目录名entry.name。注意与 native 实现的细微差异native 使用manifest.name || entryNamebuiltin.ts即空字符串也会触发回退而 Gemini 使用??nullish 合并只有null/undefined才回退空字符串会被保留为名称。五、物化为 capability 条目一个解析成功的清单会生成一个Extensioncapability 条目见 extension.ts{ name: manifest.name ?? directory-name, path: extension-directory, manifest: parsed-json, level: user | project, _source: { provider: gemini, providerName: Gemini CLI, // 由 capability 注册中心附加 path: absolute-manifest-path, level: user | project } }实现要点path是扩展目录的绝对路径extPath而非清单文件本身_source.path由createSourceMeta()归一化为绝对路径内部调用path.resolve()见 helpers.ts_source.providerName初始为空字符串由 capability 注册中心在加载时填充为 provider 的displayName即Gemini CLI见 capability/index.ts。注册中心层面的 capability 校验validate只检查name和path的存在性见 extension.tsvalidate: ext { if (!ext.name) return Missing extension name; if (!ext.path) return Missing extension path; return undefined; },清单内部字段mcpServers、tools、context在发现阶段完全不参与校验。六、错误处理与告警语义6.1 会产生警告warned的情况唯一的警告来源是非空清单文件中的 JSON 非法或语法合法但为假值的 JSON 字面量。警告格式为Invalid JSON in manifestPath在实际加载时注册中心会为警告附加 provider 显示名前缀最终形式类似[Gemini CLI] Invalid JSON in /path/to/gemini-extension.json6.2 静默跳过not warned的情况以下情况均不产生警告extensions目录本身不存在readDirEntries返回空子目录下没有gemini-extension.json清单文件不可读或为空清单 JSON 为真值但语义异常/不完整。这条边界意味着语义有效性不被强制静默失败是设计使然。排查问题时一个没生效的扩展很可能不是加载失败而是目录布局或文件名不符合发现规则如清单嵌套在更深层级、文件名拼写错误等。七、跨 provider 的优先级与去重extensionscapability 由注册中心跨 provider 聚合。当前该 capability 的 provider 有Provider实现文件优先级说明nativebuiltin.ts100主 provider扫描.omp配置geminigemini.ts60工具特定 provider扫描.gemini配置去重键为ext.name即extensionCapability.key ext ext.name见 extension.ts。7.1 跨 provider 优先级高优先级胜出注册中心按优先级从高到低插入 provider见 capability/index.ts加载时先见者胜first seen wins若native与gemini都产出了名为foo的扩展native 条目被保留优先级 100 60低优先级的重复条目仅保留在result.all中并标记_shadowed true见 capability/index.ts。7.2 provider 内部的顺序效应由于去重是先见者胜provider 内部条目的顺序会直接影响结果Gemini loader 先追加用户级再追加项目级见 gemini.ts。因此若~/.gemini/extensions与cwd/.gemini/extensions中存在同名扩展用户条目被保留项目条目被 shadowed。与之相反native provider 的getConfigDirs()顺序是项目优先、用户在后见 builtin.ts因此 native 内部的 shadowing 方向正好相反同名时项目条目胜出。这一差异在实际使用中非常关键对于 Gemini 清单用户级同名扩展会压制项目级同名扩展而 native 配置则是项目级优先。八、用户级 vs 项目级行为总结针对 Gemini 清单归纳如下维度行为扫描范围每次加载都会同时扫描用户根与项目根项目根定位固定为cwd/.gemini/extensions不做祖先目录回溯Gemini 内部同名用户优先user 先追加先见者胜跨 provider 同名高优先级 provider 胜出尤其会输给native100 60用户级门控~/.gemini/extensions受isUserSourceEnabledopt-in 门控项目级不受影响九、边界清单元数据 vs 运行时扩展模块gemini-extension.json的发现只喂养extensions元数据 capability它并不标识一个可运行的 TS/JS 入口。具体来说Gemini provider 会单独填充extension-modulescapability它扫描同样的两个扩展根目录寻找直接的.ts/.js文件、name/index.ts/index.js以及package.json中的omp/pi扩展条目见 gemini.ts 与 helpers.ts。这些模块记录与gemini-extension.json相互独立。启动路径不自动执行 Gemini 模块在discoverExtensionPaths()中环境ambient启动路径只请求nativeprovider见 loader.tsconst discovered await loadCapabilityExtensionModule(extensionModuleCapability.id, { ...loadOptions, providers: [native], });这一行为有对应的回归测试守护见 extensions-discovery.test.tsextension-modulescapability 虽然注册了 native、claude、codex、gemini、opencode 等多个 provider但discoverExtensionPaths只调用 native provider 的load()避免每次启动执行四次外部目录扫描。因此Gemini 发现的模块记录不会在此自动执行显式配置的扩展路径--extension/extensions:设置仍可正常加载。实践含义一个 Gemini 清单是可发现的元数据但清单本身或相邻的模块不会仅仅因为出现在.gemini/extensions下就被自动执行。若要让扩展模块真正运行需要通过显式路径配置接入 TS/JS 扩展加载机制详见 Extension Loading。十、实战编写与排查 Gemini 扩展清单10.1 最小可用示例在项目目录下创建如下布局your-project/ └── .gemini/ └── extensions/ └── my-extension/ └── gemini-extension.jsongemini-extension.json内容示例{ name: my-extension, description: 示例扩展声明一个 MCP 服务器与若干工具, mcpServers: { docs-server: { command: npx, args: [-y, docs-mcp], type: stdio } }, tools: [ { name: example-tool, description: 示例工具描述 } ], context: { note: 任意上下文数据发现阶段不校验结构 } }要点name缺失时回退为目录名my-extension显式声明则优先使用声明值mcpServers的配置形态与 MCP capability 的MCPServer对齐command/args/type等name与_source由发现层补齐tools与context在发现阶段不做结构校验仅原样存储于manifest中。10.2 常见排查路径如果扩展没被发现按以下顺序检查目录层级gemini-extension.json必须直接位于extensions/name/下多一层或少一层都不会被发现不递归。文件可读性文件必须非空且 JSON 合法空文件被静默跳过。项目根定位项目级扫描只认cwd/.gemini/extensions不会向上回溯若在子目录中运行检查 cwd 是否正确。用户级门控若依赖~/.gemini/extensions确认用户级源已通过enabledProviders启用或显式指定 provider。同名冲突若扩展被 native 同名扩展压制检查_shadowed标记与 provider 优先级native 100 gemini 60Gemini 内部同名时用户级优先。结语Gemini manifest extensions 机制为 oh-my-pi 提供了一条从 Gemini CLI 生态平滑接入扩展元数据的路径它以极简的目录约定extensions/name/gemini-extension.json和刻意宽松的解析语义真值门控 无 schema 校验换取低门槛兼容同时通过 capability 注册中心的优先级去重保证了与 native 配置的确定性取舍。理解清单即元数据、执行需显式接入这一边界是在实际项目中使用.gemini/extensions的关键前提。【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻