import type { Context } from '@deepseek-ai/cordis'; import z from '@deepseek-ai/schemastery'; import type { MatchConfig, RateProfile } from './matching.js'; declare module '@deepseek-ai/cordis' { interface Events { /** * `volatile()` 配置值已提交进运行中的 fiber(**不重挂**插件),只派发给 * 拥有该 entry 的那个 fiber。 * * 这条声明的正式来源是 `cordis-plugin-loader`(DSH 运行时自带),但它不在本 * 插件的依赖图里,所以这里按**同一签名**补一条,好让 `ctx.on` 通过类型检查。 * * @param paths - 变更的配置路径(键数组)。 * @mode emit */ 'loader/volatile-update'(paths: readonly (readonly string[])[]): void; } } /** * 用户配置的**明文形态**(spec §6)—— 写在 profile 的 `cordis.patch.yml` * (或 `settings.yaml`)那条 entry 的 `config:` 下。 */ export interface PeakrateOptions { /** 总开关。 */ enabled?: boolean; /** 后台刷新间隔(小时);0 = 不自动刷新。 */ refreshIntervalHours?: number; /** 本地缓存路径;默认 $DSH_HOME/dsh-peakrate/pricing.json。 */ cachePath?: string; /** 数据源地址,可换镜像/自建。 */ catalogUrl?: string; /** provider 别名覆盖。 */ providerAliases?: Record; /** 模型归属覆盖。 */ modelMappings?: MatchConfig['modelMappings']; /** 自定义 profile:新增或按 id 覆盖内置快照条目。 */ customProfiles?: unknown[]; } /** * `volatile()` 字段在运行时的形态:一个**稳定引用**(`get()` 取当前明文值)。 * * 刻意不 import `@deepseek-ai/cordis` 的 `Volatile`:本机的 cordis 是 * `^4.0.2`,而该类型是后来才导出的,用本地结构类型能让 peer 版本更宽松。 */ export interface VolatileRef { get: () => T; } /** * 校验之后交给 `apply` 的**运行时配置**:`volatile()` 的字段是 * {@link VolatileRef} 引用,其余是标量。 * * 名字与官方 cookbook(`docs/cookbook/adding-a-settings-card.md`)一致:**同一个 * 模块同时导出 `interface Config`(运行时形态)与 `const Config`(schema)** —— * 类型空间与值空间互不冲突,前者给插件读、后者给 loader 校验与设置面派发。 */ export interface Config { enabled?: boolean; refreshIntervalHours?: VolatileRef; cachePath?: string; catalogUrl?: string; providerAliases?: Record; modelMappings?: MatchConfig['modelMappings']; customProfiles?: unknown[]; } /** * 插件配置 schema —— ★ **这就是 0.1.7 的「设置命名空间注册」**。 * * DSH 0.1.7 删除了 `settings.installSection(...)` 那套安装 API(0.1.5 时代的 * 写法):现在**模块导出的 `Config` 就是唯一的注册路径** —— * `settings.describe()` 取的是 `entry.fiber.runtime.Config`,命名空间就等于 * profile 里这条 loader entry 的 `id`(即 `peakrate`,见 `cordis.patch.yml`)。 * * 标了 `volatile()` 的字段是**可在界面里即时编辑**的(无需重载插件):schema * 校验会把它解析成一个**稳定引用**,用户保存后由 loader 直接把新值写进该引用, * 并向本插件发出 `loader/volatile-update`。官方同款做法见 `llm-deepseek`。 * * 只标 `refreshIntervalHours`:`enabled` 是总开关,改它要重建 store 与下发路由, * 属于「重新装载」语义(loader 会重挂本插件),标成 volatile 反而给出 * 「已生效」的错觉。 */ export declare const Config: z; /** 后台刷新间隔(小时);0 = 不自动刷新。 */ refreshIntervalHours: z; /** 本地缓存路径;留空则用 $DSH_HOME/dsh-peakrate/pricing.json。 */ cachePath: z; /** 数据源地址,可换镜像/自建。 */ catalogUrl: z; /** provider 别名覆盖。 */ providerAliases: z, import("@deepseek-ai/cosmokit").Dict, "plain">; /** 模型归属覆盖。⚠ 字段必须与 `ModelMapping` 一一对应(`z.object` 会**丢掉** * 未声明的键 —— 漏了 `matchIsRegex` 就等于静默忽略用户写下的正则开关)。 */ modelMappings: z<({ provider?: string | null | undefined; match?: string | null | undefined; profile?: string | null | undefined; matchIsRegex?: boolean | null | undefined; } & import("@deepseek-ai/cosmokit").Dict)[], Schemastery.ObjectT; match: z; profile: z; matchIsRegex: z; }>>[], "plain">; /** 自定义 profile:新增或按 id 覆盖内置快照条目。 */ customProfiles: z; }>>, Schemastery.ObjectT; /** 后台刷新间隔(小时);0 = 不自动刷新。 */ refreshIntervalHours: z; /** 本地缓存路径;留空则用 $DSH_HOME/dsh-peakrate/pricing.json。 */ cachePath: z; /** 数据源地址,可换镜像/自建。 */ catalogUrl: z; /** provider 别名覆盖。 */ providerAliases: z, import("@deepseek-ai/cosmokit").Dict, "plain">; /** 模型归属覆盖。⚠ 字段必须与 `ModelMapping` 一一对应(`z.object` 会**丢掉** * 未声明的键 —— 漏了 `matchIsRegex` 就等于静默忽略用户写下的正则开关)。 */ modelMappings: z<({ provider?: string | null | undefined; match?: string | null | undefined; profile?: string | null | undefined; matchIsRegex?: boolean | null | undefined; } & import("@deepseek-ai/cosmokit").Dict)[], Schemastery.ObjectT; match: z; profile: z; matchIsRegex: z; }>>[], "plain">; /** 自定义 profile:新增或按 id 覆盖内置快照条目。 */ customProfiles: z; }>>, "plain">; /** * 从运行时配置里读出**当前**明文选项(`volatile` 字段每次都重新取)。 * * @param config - 校验后的运行时配置。 * @returns 明文选项。 */ export declare function readOptions(config: Config): PeakrateOptions; /** * 数据源持有者:管理当前生效的 profile 集合,并在后台按间隔刷新。 * * 读取优先级(spec §2):**本地缓存(未过期)→ 内置快照**;远端成功则替换。 * 任何一步失败都沿用上一份可用数据,UI 不受影响。 */ export declare class CatalogStore { private readonly config; private catalog; /** 当前 catalog 来自远端拉取还是内置/缓存快照 —— 供下发路由如实告知 client。 */ private catalogOrigin; private lastFetchAt; private timer; private readonly log; private readonly cachePath; private readonly catalogUrl; constructor(config: PeakrateOptions, logger: { info?: (msg: string) => void; warn?: (msg: string) => void; } | undefined); /** * 载入可用数据:**未过期**的本地缓存优先,否则用内置快照。 * * 「过期」判定(spec §2):缓存数据比内置快照更旧时即视为过期。 * 快照随插件包分发,插件升级会带来更新的快照——若盲目信任缓存, * 升级后旧缓存会一直压过新快照,直到某次远端刷新成功为止。 * 两端都缺 `updatedAt` 时回退到**文件 mtime vs 快照 mtime** 比较。 */ load(): void; /** * 判断缓存是否已过期(比内置快照旧)。 * * @param cached - 已解析的缓存目录。 * @param snapshot - 已解析的内置快照(可能不可用)。 * @returns 缓存过期时应丢弃并改用快照。 */ private isCacheStale; /** 应用 customProfiles:按 id 覆盖已有条目,或追加新条目。 */ private withCustomProfiles; /** 当前生效的 profile 列表(始终返回数组,未载入时为空)。 */ profiles(): RateProfile[]; /** 数据源更新时间(用于 UI 提示数据新鲜度)。 */ /** 目录数据来源(远端拉取 / 内置或缓存快照)。 */ origin(): 'remote' | 'builtin'; updatedAt(): string | undefined; /** * 从远端拉取一次并替换当前数据。 * * @returns 是否成功替换(失败时沿用旧数据)。 */ refresh(): Promise; /** * 改后台刷新间隔并立即生效(0 = 停止自动刷新)。 * * 供界面里修改 `refreshIntervalHours`(volatile 字段)时调用。 * * @param hours - 新间隔(小时)。 */ setRefreshInterval(hours: number): void; /** 当前生效的后台刷新间隔(小时)。 */ refreshInterval(): number; /** 启动后台刷新(refreshIntervalHours = 0 时不启动)。 */ startAutoRefresh(): void; /** 停止后台刷新。 */ stopAutoRefresh(): void; /** 上次成功拉取时间(0 = 从未)。 */ lastFetch(): number; } /** * 插件主体:建立 CatalogStore,注册为 cordis 服务,并按配置启动刷新。 * * @param ctx - cordis 上下文。 * @param config - 校验后的运行时配置(`volatile` 字段是稳定引用)。 */ export declare function apply(ctx: Context, config?: Config): void; export declare const name = "dsh-peakrate"; export declare const inject: never[];