npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

model-infra-kit

v1.0.0

Published

Embeddable model layer for AI projects: multi-provider access, model catalog, token usage and cost tracking.

Readme

model-infra-kit (mik)

可嵌入的模型层:多供应商调用、模型目录、token 用量、计价与成本统计。一个包,三个入口。

npm i model-infra-kit

版本与稳定性(1.0.0 起):这是本包第一份对外承诺——破坏性变更走主版本,新增走次版本,修复走 patch。 1.0.0 之前的 0.1.x0.3.141 个 npm 版本、42 个 Release,集中在 3 天内发完)是 pre-1.0 开发序列: 按 semver 它不承诺任何东西,只作历史保留,npm 上已统一标注为 pre-1.0。完整归纳(哪一版是什么、为什么会有这么多版本)见仓库的 CHANGELOG.md; 公共面契约见 docs/interfaces.md

第 2 步(必须,不是可选)—— 装你实际用的 provider 包: @ai-sdk/* provider 是可选 peer 依赖,只装主包时第一次调用会失败。按协议装一个,例如:

npm i @ai-sdk/openai-compatible   # openai-compatible:DeepSeek / Qwen / GLM / Kimi / 中转网关
npm i @ai-sdk/openai              # openai
npm i @ai-sdk/anthropic           # anthropic
npm i @ai-sdk/google              # google
npm i @ai-sdk/deepseek            # deepseek
npm i @ai-sdk/moonshotai          # moonshotai
npm i @ai-sdk/xai                 # xai

mik provider add 成功后若该协议的包还缺,CLI 会主动打印对应的 npm i … 一行(包已装则静默);mik serve 启动横幅也会对已配置供应商缺失的包给一行 502 预警,但不阻塞启动

| 入口 | 导出 | 用途 | |---|---|---| | model-infra-kit | ModelInfraStoreCredentialStoreProviderRegistryPricingServiceUsageServicecreateAiBridge、错误类型与全部类型 | 主路径 | | model-infra-kit/server | createServerDEFAULT_HOSTDEFAULT_PORT | 自建 HTTP 服务(mik serve 用的就是它) | | model-infra-kit/cli | main(argv)parseCliArgs、格式化工具 | 把 CLI 嵌进自己的进程 |

需要 Node ≥ 22.13(node:sqlite 自 22.13.0 起不再需要 --experimental-sqlite)。缺失的 provider 包会在 mik provider add 之后与 mik serve 启动横幅上被提前指出;库面 loadProviderFactory() 仍给出「装哪个包」的可读错误。

本包不含看板。 files 只有 distLICENSE(库 + CLI + HTTP 服务);Next.js 看板在仓库的 apps/dashboardmik dashboard 只在 monorepo 内可用,装包环境会报错并给出指引。详见项目 README 的「看板」一节


1. ModelInfra.init()

import { ModelInfra } from "model-infra-kit"

const mik = await ModelInfra.init({
  appId: "my-app",
  db: "~/.model-infra-kit/usage.db",
  providers: [{ id: "deepseek", presetId: "deepseek", apiKeyRef: "env:DEEPSEEK_API_KEY" }],
  defaultModel: "deepseek:deepseek-chat",
})

init() 不会因为「供应商缺失 / 价格目录拉不到 / 模型同步失败」而抛错,只降级并通过 onWarn 告警;只有数据库打不开或显式配置非法才是致命错误。

配置项(ModelInfraConfig + ModelInfraOptions

| 字段 | 类型 | 默认 | 说明 | |---|---|---|---| | appId | string | MIK_APP_ID"default" | 归属应用,写进每条用量事件;一库多 app 的关键 | | db | string | ~/.model-infra-kit/usage.db | SQLite 路径,":memory:" 可用 | | providers | ProviderConfig[] | — | 首次运行时注册(幂等,已存在跳过) | | defaultModel | string | — | provider:model,请求省略 model 时使用 | | syncCatalog | boolean | true | 后台发现已启用供应商的模型目录,不阻塞 init() | | recordUsage | boolean | true | 是否落库;false 时纯转发不计量 | | cacheDir | string | ~/.model-infra-kit/cache | 价格目录快照缓存目录 | | onWarn | (message, error?) => void | 丢弃 | 非致命问题回调;请勿抛错 | | baseUrl | string | http://127.0.0.1:0/v1 | 对外端点;mik serve 起来后由 setBaseUrl() 注入真实端口 | | maxRetries | number | AI SDK 默认 | 每次供应商调用的重试次数 | | pricingCatalog | PricingCatalog | — | 注入 llm-pricing 目录(离线/测试) | | pricingFetch | typeof fetch | globalThis.fetch | 注入一个抛错的 fetch 即可完全离线 | | onUsage | (event) => void | — | 每条事件落库后回调;抛错被吞并转 onWarn |

实例成员

| 成员 | 签名 | 说明 | |---|---|---| | appId | readonly string | 本实例的归属应用 | | baseUrl | readonly string | OpenAI 兼容端点(给客户端用) | | fetch | readonly typeof fetch | 可直接传给 new OpenAI({ fetch }) 的适配器 | | ai | AiBridge | languageModel() / test() / discoverModels() | | providers | ProviderRegistry | 见 §4 | | models | ModelCatalog | 见 §5 | | pricing | PricingService | 见 §6 | | usage | UsageService | 见 §7 | | catalogSync | Promise<void> | 后台目录同步的 promise;init() 不等它 | | generate(req) | Promise<ModelResponse> | 非流式,成功失败都计量 | | stream(req) | AsyncIterable<StreamEvent> | 流式;usage/finish 事件在流结束后发出 | | resolveModel(ref?) | { providerId; modelId; requested } | provider:model 直拆;裸名用默认供应商;都缺抛 INVALID_REQUEST | | setBaseUrl(url) | void | 服务端绑定端口后回填 | | close() | Promise<void> | 关闭 SQLite 连接(等后台目录同步最多 5s;之后所有公开成员抛 STORAGE) |


2. generate() / stream()

const reply = await mik.generate({
  model: "deepseek:deepseek-chat",           // 省略则用默认模型
  messages: [{ role: "user", content: "hi" }],
  system: "You are terse.",
  temperature: 0.2,
  maxTokens: 512,
  tools: { get_weather: tool({ /* AI SDK tool */ }) },
  tags: { feature: "chat" },                 // 任意键值,随事件落库
  sessionId: "conv-42",                      // 用于按会话查询
})

for await (const event of mik.stream({ messages: [{ role: "user", content: "hi" }] })) {
  if (event.type === "text_delta") process.stdout.write(event.text)
  if (event.type === "usage") console.log(event.usage, event.cost.usd)
  if (event.type === "error") console.error(event.error.code, event.error.message)
}

ModelRequest 字段

| 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | model | string | — | provider:model;裸模型名走默认供应商;都没有则抛 INVALID_REQUEST | | messages | ModelMessage[] | ✅ | AI SDK 消息数组(system 角色请用顶层 system 字段) | | system | string | — | 系统提示词 | | tools | ToolSet | — | AI SDK 工具集;工具循环默认最多 5 步 | | temperature | number | — | 采样温度 | | maxTokens | number | — | 输出上限 | | headers | Record<string,string> | — | 透传给供应商的额外请求头 | | tags | Record<string,string> | — | 随用量事件落库,便于归因 | | sessionId | string | — | 随用量事件落库,可 usage.query({ sessionId }) | | signal | AbortSignal | — | 取消 |

ModelResponse 字段

| 字段 | 类型 | 说明 | |---|---|---| | text | string | 最终文本 | | toolCalls | ToolCall[] | { id, name, input } | | finishReason | string | 供应商给出的结束原因 | | usage | TokenUsage | 四类 token + reasoning(缺失按 0 展示) | | cost | CostInfo | 金额、区间、依据、价格来源 | | provider | string | 实际供应商 id | | model | { requested; actual } | 请求的引用 vs 供应商回显的模型名 | | latencyMs | number | 端到端耗时 | | firstTokenMs | number? | 首 token 延迟(流式才有) | | steps | number? | 工具循环步数 |

StreamEvent 变体

| type | 载荷 | 说明 | |---|---|---| | text_delta | { text: string } | 文本增量 | | tool_call_delta | { id; name; delta } | 工具调用参数增量 | | tool_call_complete | { call: ToolCall } | 一个工具调用组装完成 | | step_finish | { finishReason; usage } | 单步结束 | | usage | { usage: TokenUsage; cost: CostInfo } | 本次调用已计价(流结束时发出) | | finish | { response: ModelResponse } | 终态汇总 | | error | { error: { code; message } } | 失败(已脱敏) |

TokenUsage / CostInfo

| 类型 | 字段 | |---|---| | TokenUsage | inputoutputcacheReadcacheWritereasoning(均为 number,缺失按 0 呈现) | | CostInfo | usdlowhighbasis(类型是 PriceBasis \| "manual"exact\|flat\|blended\|unknown,外加 manual)、source(类型是 PriceSource \| "manual"override\|modelsdev\|openrouter\|fallback\|provider\|missing,外加 manual)、pricingModel?providerId? |

枚举必须按联合类型读,不能按基础类型读PriceBasis / PriceSourcesrc/types.ts)本身不含 manual,而 CostInfo.basis / CostInfo.source 的类型是 PriceBasis | "manual" / PriceSource | "manual"manual 由两条真实路径写入——手动价(pricing/service.tsmanualCost)与宿主自报金额(server/api.tsserver.test.ts 有断言)——所以它是存在的取值,删掉它才会变成假事实。

basis / source 的枚举字面unknownsource=missing 是同一件事——没有任何可用的计价依据,此时 usd=low=high=0,且不会被插值或估算成一个数字;0 不等于免费。手动价记 basis=flat(套用了一个真实费率)。注意区分两套同形枚举:CostInfo.sourcemodelsdev(无下划线)是成本来源(models.dev 目录),而 ModelInfo.sourcemodels_dev(带下划线)是模型目录来源,两者不可互换。

内部计价保留「字段缺失 ≠ 0」的语义(传给 llm-pricing 的是 Partial<TokenUsage>),公共类型只是展示层。


3. fetch 适配器

const client = new OpenAI({ apiKey: "unused", baseURL: mik.baseUrl, fetch: mik.fetch })
  • 请求体的 model 决定供应商;调用方自带的 authorization / api-key / x-api-key / x-goog-api-key 会被剥离,改由 mik 按 provider.protocol 附上凭据。
  • 响应原样返回,mik 只读克隆来提取 usage 并计价,落库 source = "fetch"
  • 流式响应同样计量(isStreaming: true)。
  • 供应商 id 未配置 → HTTP 404 PROVIDER_NOT_FOUND;缺凭据 → 401 CREDENTIAL

4. mik.providersProviderRegistry

| 方法 | 签名 | 说明 | |---|---|---| | list() | ProviderRecord[] | 全部供应商(全局共享,不按 appId 过滤) | | get(id) | ProviderRecord \| null | 单个 | | add(config) | ProviderRecord | 增改;未给 protocol 时按 presetId 补全;id 需匹配 /^[A-Za-z0-9._-]{1,64}$/(禁 :) | | remove(id) | boolean | 删除 | | setEnabled(id, enabled) | void | 启停 | | resolve(id) | ResolvedProvider | { record, apiKey, apiKeySource, baseUrl, protocol, npmPackage };缺失抛 PROVIDER_NOT_FOUND / CREDENTIAL | | defaultModel() | string \| null | 形如 "deepseek:deepseek-chat" | | setDefaultModel(ref) | void | 校验 provider:model 与供应商存在 | | seed(configs) | void | 幂等注册 |

apiKeySource 取值:"ref"apiKeyRef 命中)、"env"(回退到 preset 的 envKey)、"none"(该供应商不需要密钥)。密钥永不落库、永不进日志providers 表只存 api_key_ref


5. mik.modelsModelCatalog

| 方法 | 签名 | 说明 | |---|---|---| | list(providerId?) | ModelInfo[] | 目录(可含 models.dev 与 provider API 来源) | | get(ref) | ModelInfo \| null | provider:model,附带价格卡 | | refresh(providerId) | Promise<ModelInfo[]> | 调供应商 API 重新发现;空结果不会清空已有目录 |

ModelInfoproviderIdmodelIdrefdisplayNamecontextWindow?maxOutputTokens?capabilities{text,image,toolCall,reasoning,structuredOutput}pricing?sourceprovider_api\|models_dev\|preset\|manual)、syncedAt?


6. mik.pricingPricingService

| 方法 | 签名 | 说明 | |---|---|---| | init() / refresh() | Promise<PricingState> | 加载/刷新目录;永不抛错,失败降级为 stale/error | | state() | { status: "fresh"\|"stale"\|"error"; loadedAt?; source?; lastError? } | 当前状态 | | estimate(input) | CostInfo | { model, at?, usage: Partial<TokenUsage> } → 金额 | | priceFor(model, at?, facts?) | ModelPricing \| null | 单价卡(含 contextTierAbove / reasoningMode) | | setOverride(o) | void | 手动价,优先级最高;至少给 inputPerMoutputPerM,否则 INVALID_REQUEST | | removeOverride(modelId) | boolean | 撤销手动价 | | listOverrides() | PricingOverride[] | 手动价列表 | | candidates(model) | string[] | llm-pricing 的匹配候选,供 UI 展示 |

价格优先级:手动价 pricing_overrides > llm-pricing overrides > 上游目录 > 内置 archive 兜底。断网时用本地快照继续计价(状态标 stale);历史成本永不重算——事件落库即固化 cost / pricing_source / pricing_basis


7. mik.usageUsageService

| 方法 | 签名 | 说明 | |---|---|---| | record(event) | boolean | 幂等写入(requestId 重复返回 false);正常由 generate/stream/fetch 自动调用 | | summary(query?) | UsageSummary | 请求数、成功/失败、成本(含 low/high)、四类 token、缓存命中率、平均延迟 | | trends(query?, bucket?) | UsageTrendPoint[] | 按 "day"(默认)或 "hour" | | byProvider(query?) / byModel(query?) | UsageBucket[] | 分组汇总 | | query(filter?) | UsagePage | 明细分页 { total, events } | | get(requestId, options?) | UsageEvent \| null | 默认只查本实例 appId{ appId: "" } 显式放开(调试用) | | rollupAndPrune(now?, retentionDays?) | number | 全局维护:把所有 app 的过期明细折进 rollup 并删除 | | clear() | number | 清空本 app 的明细 |

UsageQueryfromto(epoch ms)、appIdproviderIdmodelstatusok\|error)、sessionIdlimitoffset

UsageEvent(明细):requestIdappIdtssourcegenerate\|stream\|fetch)、providerIdmodelRequestedmodelActualpricingModel?usagecostlatencyMs?firstTokenMs?statuserrorCode?isStreamingsessionId?tags?pricingBasis?pricingSource?

金额一律以整数微美元在 SQL 里聚合(SUM(CAST(ROUND(cost*1000000) AS INTEGER))),返回前才转回美元,避免浮点求和误差。


8. 低层入口

import { Store, ProviderRegistry, CredentialStore, PricingService, UsageService } from "model-infra-kit"
import { createServer } from "model-infra-kit/server"

const store = await Store.open({ path: "usage.db", driver }) // driver 可选:注入 better-sqlite3
const server = await createServer({ hub: mik, port: 3211, host: "127.0.0.1", token: process.env.MIK_SERVER_TOKEN })
console.log(server.url, server.port)
await server.close()

Store.open({ path?, driver? }){ providers, models, pricing, usage, settings, driver, close() }createServer(options){ url, port, host, sseClients, close() }port: 0 取随机端口;token 一旦设置,除 GET /api/health 外都需要 Authorization: Bearer <token>


9. 错误

import { ModelInfraError, isModelInfraError } from "model-infra-kit"

try {
  await mik.generate({ messages })
} catch (error) {
  if (isModelInfraError(error)) console.error(error.code, error.message, error.retryable)
}

ModelInfraErrorCodeAUTHCONNECTIONRATE_LIMITMODEL_NOT_FOUNDPROVIDER_NOT_FOUNDINVALID_REQUESTPROVIDERTIMEOUTPRICING_UNAVAILABLECREDENTIALSTORAGEUNKNOWN

message 已脱敏,可直接展示给用户;原始错误保留在 cause


相关文档