首页
/ Jan @janhq/core 核心库详解:事件系统、扩展生命周期与从零构建自定义扩展

Jan @janhq/core 核心库详解:事件系统、扩展生命周期与从零构建自定义扩展

2026-09-05 11:27:26作者:姚月梅Lane

本文围绕 Jan 仓库中的 core/README.md 展开,系统讲解 TypeScript 核心库 @janhq/core 的三大职责——与核心 API 通信、注册应用扩展、导出类型定义,并结合 core/src 下的源码剖析事件系统(events.on/off/emit)、BaseExtension 生命周期与设置持久化机制,最后完整复现"下载模板 → 编写 onLoad()yarn build → 手动安装 .tgz"的扩展开发全流程,帮助读者掌握为 Jan 编写自定义扩展所需的完整技术栈。

一、@janhq/core 的定位与包结构

按照 core/README.md 的定义:

This module includes functions for communicating with core APIs, registering app extensions, and exporting type definitions.

@janhq/core 提供三类能力:调用 Jan 核心 API 的桥接函数扩展注册所需的基础类与类型全套 TypeScript 类型定义。从 core/package.json 可以看到该包的关键元信息:

  • 包名 @janhq/core,入口 dist/index.js,类型声明位于 dist/types/index.d.ts
  • 运行时依赖仅有 rxjsulidx(工具 ID 生成),react@19.0.0 作为 peerDependency,说明该库面向前端/扩展运行时设计;
  • 许可协议为 AGPL-3.0,包管理器锁定为 yarn@4.5.3

构建链在 core/package.jsonscripts 中定义:

"prebuild": "rimraf dist",
"build": "tsc -p . && rolldown -c rolldown.config.mjs"

即先用 tsc 产出类型声明,再由 core/rolldown.config.mjs 以 ESM 格式打包 src/index.tsdist/index.js,并注入 VERSION 等构建时常量。测试基于 Vitest(yarn testyarn test:watchyarn test:coverage)。

模块导出结构

库的顶层入口 core/src/index.ts 只做两件事:

export * from './types'    // 导出所有类型定义
export * from './browser'  // 导出浏览器/扩展运行时模块
declare global {
  var core: any | undefined // 宿主应用注入的全局对象
}

注意 declare global 声明的 globalThis.core:这是 Jan 宿主应用(web-app / Tauri 前端)注入的全局对象,扩展运行期通过它访问真正的事件总线与 API 实现。这也是为什么 README 中将 import * as core from '@janhq/core' 标注为 "Web / extension runtime"——该库本身只是桥接层,能力由宿主应用提供。

core/src/browser/index.ts 进一步细分了七大模块:

模块 导出文件 职责
Core ./core 核心 API 桥接
Events ./events 事件订阅/发布
Filesystem ./fs 文件系统操作
Extension ./extension BaseExtension 基类
Extensions ./extensions 内置抽象扩展(assistant、conversational、inference、MCP、RAG、VectorDB、engines)
Models ./models ModelManager 与模型类型
Logger ./logger 共享日志器

这与 core/CONTRIBUTING.md 中列出的关键目录(/src/browser 核心 API、/src/browser/extensions 内置扩展、/src/types 类型定义、/src/test 测试工具)一一对应,其阐述的四条设计原则也值得记住:平台无关(浏览器/Node 均可运行)、基于扩展(新能力 = 新扩展)、全面类型化(TypeScript 强制)、事件驱动(组件间通过事件通信)

二、事件系统:扩展与应用的解耦通道

README 扩展示例的第一行核心逻辑就是:

core.events.on(MessageEvent.OnMessageSent, (data) => MyExtension.inference(data, this))

事件系统的实现在 core/src/browser/events.ts,结构非常精简:

const on: (eventName: string, handler: Function) => void = (eventName, handler) => {
  globalThis.core?.events?.on(eventName, handler)
}
const off: (eventName: string, handler: Function) => void = (eventName, handler) => {
  globalThis.core?.events?.off(eventName, handler)
}
const emit: (eventName: string, object: any) => void = (eventName, object) => {
  globalThis.core?.events?.emit(eventName, object)
}

export const events = { on, off, emit }

三个方法全部委托给 globalThis.core.events,即真正的 EventBus 由宿主应用持有,@janhq/core 只是类型安全的薄封装。core/CONTRIBUTING.md 给出了通用用法示例:

// Emit events
events.emit('model:loaded', { modelId: 'llama-3' })

// Listen for events
events.on('model:loaded', (data) => {
  console.log('Model loaded:', data.modelId)
})

内置事件名枚举

消息相关的标准事件名定义在 core/src/types/message/messageEvent.ts

export enum MessageEvent {
  /** The `OnMessageSent` event is emitted when a message is sent. */
  OnMessageSent = 'OnMessageSent',
  /** The `OnMessageResponse` event is emitted when a message is received. */
  OnMessageResponse = 'OnMessageResponse',
  /** The `OnMessageUpdate` event is emitted when a message is updated. */
  OnMessageUpdate = 'OnMessageUpdate',
}

配合 core/src/types/message/messageRequestType.ts 中的 MessageRequestTypeThread / Assistant / Summary),扩展就能按消息来源区分处理逻辑。MessageRequestDataThreadContentThreadMessageContentType 等事件载荷类型则统一由 core/src/types 导出,这正是 README 中"导出类型定义"职责的具体体现。

三、扩展系统:BaseExtension 与生命周期

3.1 BaseExtension 基类

所有扩展必须继承 core/src/browser/extension.ts 中的抽象类:

export abstract class BaseExtension implements ExtensionType {
  protected settingFolderName = 'settings'
  protected settingFileName = 'settings.json'

  name: string        // 扩展名
  productName?: string
  url: string         // 扩展加载地址
  active              // 是否激活
  description
  version

  constructor(url, name, productName?, active?, description?, version?) { ... }

  type(): ExtensionTypeEnum | undefined { return undefined } // 子类可覆写

  abstract onLoad(): void     // 扩展加载时的初始化逻辑
  abstract onUnload(): void   // 扩展卸载时的清理逻辑

  compatibility(): Compatibility | undefined { return undefined }
}

几个关键点:

  1. onLoad() / onUnload() 是抽象方法,子类必须实现——初始化逻辑放在 onLoad,清理逻辑放在 onUnload,这与 README 中"在 onLoad() 方法里添加你的代码"的指引完全一致;
  2. type() 返回扩展类型,应用据此判断该扩展是否扩展了某个已知能力。可用类型枚举定义在同文件 ExtensionTypeEnum
export enum ExtensionTypeEnum {
  Assistant = 'assistant',
  Conversational = 'conversational',
  Inference = 'inference',
  Model = 'model',
  SystemMonitoring = 'systemMonitoring',
  MCP = 'mcp',
  HuggingFace = 'huggingFace',
  Engine = 'engine',
  Hardware = 'hardware',
  RAG = 'rag',
  VectorDB = 'vectorDB',
}
  1. compatibility() 用于环境兼容性检查,返回 Compatibilityplatform: string[]version: string),从源码结构看,宿主在加载扩展时会用它判断当前平台与版本是否匹配。

3.2 模型注册与设置持久化

BaseExtension 还内建了模型注册和设置管理能力,对第三方扩展非常实用:

  • 模型注册extension.ts#L104-L108):registerModels(models: Model[]) 将模型逐个注册进内存中共享的 ModelManager.instance(),本地推理类扩展(如仓库中的 llamacpp-extension)即依赖该机制上报模型列表;
  • 设置注册extension.ts#L115-L156):registerSettings(settings: SettingComponentProps[]) 会先读取 localStorage 中同名的旧设置,合并保留用户已设置的值、options 与 recommended 字段,再写回 localStorage(key 为扩展名)。这解释了为什么扩展重命名(name 字段变化)后设置会"丢失"——存储 key 绑定在 name 上;
  • 设置读取与更新getSetting<T>(key, defaultValue)extension.ts#L164-L169)按 key 取值并支持默认值;updateSettings(componentProps)extension.ts#L207-L232)更新后会对每一项触发 onSettingUpdate(key, value) 回调,子类覆写该回调即可响应设置变化;
  • 前置安装钩子install(): Promise<void> 默认空实现,扩展可覆写用于下载依赖等安装期准备。

3.3 内置抽象扩展

core/src/browser/extensions/index.ts 导出了若干面向特定领域的抽象扩展,自定义扩展可继承它们来"扩展一个已知能力"而非从零实现:

  • ConversationalExtensionconversational.ts):实现线程与消息的增删查改,抽象方法包括 listThreadscreateThreadcreateMessagelistMessagesmodifyMessagegetThreadAssistant 等;
  • InferenceExtensioninference.ts):只要求实现 inference(data: MessageRequest): Promise<ThreadMessage>,是自定义推理逻辑的挂载点;
  • AssistantExtensionassistant.ts):要求实现 createAssistant / deleteAssistant / getAssistants
  • 此外还有 MCPExtension(工具与服务器通信)、RAGExtensionVectorDBExtension 以及 engines(AI 引擎基类)。

例如仓库内的 extensions/llamacpp-extension 就是一个推理引擎扩展的完整实例,可作为学习参考。

3.4 文件系统桥接

扩展读写数据依赖 core/src/browser/fs.ts 导出的 fs 对象,它把一系列操作转发到 globalThis.core.api

export const fs = {
  writeFileSync, readFileSync, existsSync, readdirSync,
  mkdir, rm, mv, unlinkSync, appendFileSync, copyFile,
  fileStat, writeBlob, getGgufFiles,
}

这些函数与 Node 的 fs API 命名一致,但路径约定上支持 file:// 协议(指向 Jan 数据目录)。从 extensions/assistant-extension/src/index.ts 的实现可以看到真实用法:onLoad() 中先 fs.existsSync('file://assistants') 检查目录、不存在则 fs.mkdir,再执行数据迁移、读取已有 assistant 列表,为空时创建带默认采样参数(temperature: 0.7top_k: 20top_p: 0.8repeat_penalty: 1.12)的默认 assistant。这展示了 onLoad + fs + 默认值兜底的典型初始化模式。

四、从零构建一个 Jan 扩展(README 全流程复现)

以下内容完整继承 core/README.md 的 "Build an Extension" 章节,并结合仓库补充细节。

第 1 步:获取扩展模板

README 建议下载官方扩展模板(extension-template 仓库)作为起点。也可以直接参考本仓库 extensions/ 目录下的 7 个内置扩展(assistant、conversational、download、llamacpp、mlx、rag、vector-db)的目录结构:每个扩展都是一个独立包,含 src/index.tspackage.jsonrolldown.config.mjstsconfig.jsonvitest.config.ts 等文件。

第 2 步:修改源码

  1. 在编辑器中打开 index.ts
  2. 把示例扩展类 SampleExtension 重命名为你期望的扩展名(注意:name 同时是设置持久化的 localStorage key,改名会影响已有设置的读取,见 3.2 节);
  3. 引入 core 包:
import * as core from '@janhq/core'
  1. onLoad() 方法中添加你的代码。README 给出的完整示例是监听消息事件并提供自定义推理逻辑
// Example of listening to app events and providing customized inference logic:
import * as core from '@janhq/core'

export default class MyExtension extends BaseExtension {
  // On extension load
  onLoad() {
    core.events.on(MessageEvent.OnMessageSent, (data) => MyExtension.inference(data, this))
  }

  // Customized inference logic
  private static inference(incomingMessage: MessageRequestData) {
    // Prepare customized message content
    const content: ThreadContent = {
      type: ContentType.Text,
      text: {
        value: "I'm Jan Assistant!",
        annotations: [],
      },
    }

    // Modify message and send out
    const outGoingMessage: ThreadMessage = {
      ...incomingMessage,
      content,
    }
  }
}

结合前文源码可以读出该示例的每个符号来源:BaseExtensionMessageEventMessageRequestDataThreadContentThreadMessageContentType 均从 @janhq/core 导出;core.events.on 实际注册到宿主的全局事件总线。若要监听模型加载、线程变更等其他事件,可参考 core/src/types 下 message、thread、model、assistant 等子目录导出的事件枚举,并用 core.events.offonUnload() 中注销监听器,与 onLoad/onUnload 的生命周期约定保持一致。

第 3 步:构建扩展

  1. 进入扩展目录;
  2. 安装依赖:
yarn install
  1. 编译源码:
yarn build

README 特别说明:该命令会保持在前台持续运行,并在你每次修改源码后自动重新构建(watch 模式),因此开发期间终端会一直挂着,Ctrl+C 即可退出。构建产物中会生成 .tgz 安装包。

  1. 在 Jan 应用中手动安装:进入 Settings > Extension > Manual Installation,选择生成的 .tgz 文件完成安装。安装成功后,该扩展会在应用启动时被实例化并触发其 onLoad()

五、本地开发与测试 @janhq/core 本身

如果需要直接开发核心库(而非基于它写扩展),core/CONTRIBUTING.md 给出了一组命令:

# Build the SDK
yarn build

# Run tests
yarn test

# Watch mode
yarn test:watch

测试用例与源文件同目录(如 core/src/browser/extension.test.tscore/src/browser/events.test.ts),采用 Vitest 的 describe/it/expect 风格,例如:

describe('MyFeature', () => {
  it('should do something', () => {
    const result = doSomething()
    expect(result).toBe('expected')
  })
})

开发规范(同文件 "Best Practices" 一节)要求:保持简单、全面使用 TypeScript(禁用 any)、为关键功能写测试、遵循既有模式、并在 index 文件中导出新增模块。

六、适用前提与限制

  • 运行环境@janhq/core 的设计前提是存在宿主注入的 globalThis.core(见 core/src/index.ts 的全局声明与 events.tsfs.ts 的委托实现)。脱离 Jan 宿主应用单独 import 该库时,events/fs 调用会得到 undefined,类型定义与基类本身仍可使用;
  • 设置存储位置:扩展设置持久化在 localStorage(key 为扩展 name),而非扩展目录下的 settings.json——后者是 settingFolderName/settingFileName 约定的默认名称,从源码结构看主要用于扩展自身的默认设置文件;
  • 版本能力:本文以当前仓库 @janhq/core 0.1.10(见 core/package.json)的实际代码为准,事件名、扩展类型枚举与 BaseExtension 方法签名均以上述源码文件为权威依据。

小结

@janhq/core 是 Jan 扩展生态的地基:events 提供宿主与应用间的事件总线、BaseExtension 定义了 onLoad/onUnload 生命周期与设置持久化、fs 提供 file:// 数据目录访问、types 目录输出全部类型定义。掌握 README 中的四步流程(模板 → 改 onLoadyarn build watch 构建 → 手动安装 .tgz)后,即可参照 extensions/ 下的内置扩展为 Jan 编写自己的 assistant、推理或 RAG 扩展。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
983
503
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384