开发指南 功能指南 AI Token 计量与恢复 配置模型价格,接入 AI SDK / OpenRouter,持久保存用量并恢复原扣款。
SDK 0.0.1 更新于 2026年10月14日
OpenRouter 自动采集 宿主安装 @openrouter/sdk;当前适配 chat.send。下面是业务片段,allocateDurableProviderCallContext 与 saveUsageRecord 必须由宿主实现:每次上游调用先持久分配独立幂等键,绑定已验证 customerId、entitlementId 和可选 entityId,并保存完整记录。不能用只存在内存的随机数或空回调替代。
配置价格键须覆盖响应的真实模型(openrouter/ 前缀),不是只覆盖请求路由名称。reasoning: output 把推理计入输出价,separate 使用独立推理价。缺少最终 Token 或价格明确报错;显式免费模型记录真实 Token 和零费用。流式调用在结束时结算,取消或中断保存 incomplete,不推算用量。
server/openrouter.ts · fragment TypeScript 复制 1 import { OpenRouter } from "@openrouter/sdk" ;2 import { createOpenRouterUsageClient } from "@tinkerlandai/indiana-sdk/openrouter" ;3 4 const openRouter = createOpenRouterUsageClient ({5 openRouter: new OpenRouter ({ apiKey: process.env.OPENROUTER_API_KEY ! }), 6 pricing, 7 client: groove, 8 reasoning: "output" , 9 context: () => allocateDurableProviderCallContext (), 10 persist: (record) => saveUsageRecord (record), 11 }); 12 await openRouter.chat.send ({13 chatRequest: { 14 model: configuredOpenRouterModelId, 15 messages: [{ role: "user" , content: prompt }], 16 stream: false , 17 }, 18 }); AI SDK v7 与失败恢复 中间件支持 LanguageModelV4;模型和供应商由宿主提供。每个工具步骤独立计量,不能共用整个用户请求的幂等键。记录依次保存 captured、quoted、settled;先保存真实用量和完整原报价,再原子消费。模型生成与余额消费不是跨服务事务,额度不足或存储失败会明确抛错。
上面的恢复片段适用于 AI SDK quoted 记录。OpenRouter 用 trackOpenRouterUsage,并传回原 generationId、真实模型、usage 和 quote,以保留供应商成本。收费响应丢失只重放原扣款,不重新调用模型或按新价格重报价。实际用量含零费用事件可用 readUsageAnalytics;积分来源分配分析使用 readCreditAnalytics,不把不同单位直接相加。
server/ai-sdk.ts · fragment TypeScript 复制 1 import { generateText, wrapLanguageModel } from "ai" ;2 import { createAiUsageMiddleware } from "@tinkerlandai/indiana-sdk/server" ;3 4 const model = wrapLanguageModel ({5 model: configuredProviderModel, 6 middleware: createAiUsageMiddleware ({ 7 pricing, client: groove, modelId: configuredPricingModelId, 8 reasoning: "output" , 9 context: () => allocateDurableProviderCallContext (), 10 persist: (record) => saveUsageRecord (record), 11 }), 12 }); 13 await generateText ({ model, prompt });server/recover-ai-charge.ts · fragment TypeScript 复制 1 // Restore the saved pricing helper, context and complete original quote. 2 // OpenRouter recovery also preserves generationId and providerCostUsd. 3 const result = await pricing.trackTokens (groove, {4 ...savedRecord.context, 5 quote: savedRecord.quote, 6 }); 7 if (!result.receipt.accepted) throw new Error ("AI settlement rejected" );