@tinkerlandai/indiana-sdk 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +306 -2
- package/THIRD_PARTY_NOTICES.md +19 -0
- package/dist/ai-usage-CzA11PV5.d.ts +235 -0
- package/dist/api-DDlcUvTv.d.ts +7128 -0
- package/dist/chunk-6X42DWOM.js +1 -0
- package/dist/chunk-IUDHYOKC.js +21 -0
- package/dist/chunk-OIWEFX7U.js +1 -0
- package/dist/chunk-TDSKWZTO.js +1 -0
- package/dist/coupon-gncDkBtm.d.ts +161 -0
- package/dist/feature-client-CyYevYy2.d.ts +450 -0
- package/dist/funnel-runtime-C05ZECiP.d.ts +5052 -0
- package/dist/funnel-runtime-host-DWRQK5U2.js +4 -0
- package/dist/index.d.ts +3640 -0
- package/dist/index.js +1 -0
- package/dist/native-shell.d.ts +4 -0
- package/dist/native-shell.js +1 -0
- package/dist/openrouter.d.ts +41 -0
- package/dist/openrouter.js +1 -0
- package/dist/popup.d.ts +274 -0
- package/dist/popup.js +178 -0
- package/dist/server.d.ts +766 -0
- package/dist/server.js +1 -0
- package/dist/src-MCXYJ2QR.js +1 -0
- package/package.json +72 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Release facts and artifact evidence: [SDK release ledger](../../docs/sdk-releases.md). Earlier personal-package versions are preserved in [@houzhen888/groove-grow history](../../docs/sdk-changelog-groove-grow.md). The two packages have independent version histories.
|
|
4
|
+
|
|
5
|
+
## 0.0.1 — 2026-10-13
|
|
6
|
+
|
|
7
|
+
- First organizational Beta under `@tinkerlandai/indiana-sdk`, replacing personal-package imports. Publish with the `beta` tag and pin `0.0.1` when installing.
|
|
8
|
+
- Include browser, server, popup, OpenRouter and native-shell ESM entries. Private contracts remain bundled into the SDK.
|
|
9
|
+
- Add browser/server `createFeatureClient` with scoped cached configuration and synchronous GrowthBook evaluation. Generic decisions return immutable `value` JSON with live validity `state`; trusted inputs use `attributes`.
|
|
10
|
+
- Replace customer `metadata` / `tags` inputs with `attributes`, `removeAttributes` and incremental `tagChanges`. No obsolete-format compatibility reader is included.
|
|
11
|
+
- Refine Funnel decision recovery and host resume/close behavior. Include current subscription, native-receipt and prepaid Feature quantity contracts, with the matching generated native host.
|
|
12
|
+
- Include GrowthBook MIT attribution and synchronize installation examples and downloadable archives.
|
|
13
|
+
|
|
14
|
+
Upgrade imports and the matching server/database deployment together. This npm release does not deploy Grow or publish the separate Swift/Kotlin packages. Installed-artifact and offline checks do not certify real payments, native stores or deployed behavior. Browser validation was explicitly omitted at the user's request.
|
package/README.md
CHANGED
|
@@ -1,3 +1,307 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @tinkerlandai/indiana-sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Indiana 的框架无关 Web / TypeScript SDK。组织包首版为 `0.0.1`,以 `beta` 标签分发;升级前确认服务端与数据库迁移匹配。不提供旧接口或数据格式兼容,变更见 [Changelog](CHANGELOG.md)。
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install @tinkerlandai/indiana-sdk@0.0.1
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
安装请固定 `0.0.1`。新组织包与旧个人包 `@houzhen888/groove-grow` 的版本空间独立,不能按版本号大小判断升级关系。完整发布记录与核对时间见 [发布总账](../../docs/sdk-releases.md),本版范围与验收边界见本文末尾。
|
|
10
|
+
|
|
11
|
+
服务端 Key、Customer Token 与控制台登录的区别、逐请求查库校验、权限及撤销轮换,见 [服务端鉴权](../../docs/server-authentication.md)。安装与发布状态见 [自助接入](../../docs/sdk-selfserve.md)。
|
|
12
|
+
|
|
13
|
+
一个包提供浏览器、可信后端与渲染入口,不依赖 React、Next.js 或 Supabase 客户端。内部 contracts 在打包时内联,接入方不需要安装私有工作区包。
|
|
14
|
+
|
|
15
|
+
| 入口 | 用途 |
|
|
16
|
+
| ---------------------------------------- | -------------------------------------------------------------- |
|
|
17
|
+
| `@tinkerlandai/indiana-sdk` | 当前客户读取、Funnel 控制器与统一展示宿主 |
|
|
18
|
+
| `@tinkerlandai/indiana-sdk/server` | 客户管理、Session 签发、账本写入与可信 Funnel 启动 |
|
|
19
|
+
| `@tinkerlandai/indiana-sdk/openrouter` | 可选 OpenRouter 用量集成,宿主需安装对应 peer dependency |
|
|
20
|
+
| `@tinkerlandai/indiana-sdk/popup` | 纯屏幕渲染、清洗和编辑预览 |
|
|
21
|
+
| `@tinkerlandai/indiana-sdk/native-shell` | 供 Grow 托管的原生 Funnel 壳生成纯设计资源,不含客户或运行凭据 |
|
|
22
|
+
|
|
23
|
+
## 可信后端与客户身份
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { createGrooveServer } from "@tinkerlandai/indiana-sdk/server";
|
|
27
|
+
const groove = createGrooveServer({
|
|
28
|
+
baseUrl: process.env.GROOVE_BASE_URL!,
|
|
29
|
+
apiKey: process.env.GROOVE_SECRET_KEY!,
|
|
30
|
+
scope: { projectId: process.env.GROOVE_PROJECT_ID!, environment: "sandbox" },
|
|
31
|
+
});
|
|
32
|
+
const user = await requireSignedInUser(request);
|
|
33
|
+
const { customer } = await groove.getOrCreateCustomer({
|
|
34
|
+
externalUserId: user.id,
|
|
35
|
+
});
|
|
36
|
+
const { customerToken } = await groove.createCustomerSession(customer.id);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Secret Key 只放在可信后端。浏览器提供的客户 ID 不能作为登录证明;客户 Token 仅交给该已登录用户,SDK 只在内存持有。每个 App/环境单独配置权限与允许来源。切换账号时关闭并销毁旧运行,丢弃旧客户凭据;迟到响应不能更新新身份。
|
|
40
|
+
|
|
41
|
+
## 统一 Funnel
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { createFunnelStartClient } from "@tinkerlandai/indiana-sdk/server";
|
|
45
|
+
const funnels = createFunnelStartClient({
|
|
46
|
+
baseUrl: process.env.GROOVE_BASE_URL!,
|
|
47
|
+
apiKey: process.env.GROOVE_SECRET_KEY!,
|
|
48
|
+
scope: { projectId: process.env.GROOVE_PROJECT_ID!, environment: "sandbox" },
|
|
49
|
+
});
|
|
50
|
+
// 宿主在首次调用前持久保存整个 start 输入;响应丢失后原样重试。
|
|
51
|
+
const result = await funnels.start({ ...retainedStartInput, customerToken });
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`retainedStartInput` 包含 `source`、`requestId`,以及明确的 `commerce`、`locale`、`parameters`。`source` 选择 `funnel/id`、`paywall/id` 或已发布的 `trigger/key`。服务端 Key 需要 `sessions:create` 与 `funnels:run`。每个运行都属于一个客户:已登录用户用 `createCustomerSession` 的 `customerToken`;未登录访客先 `createGuestCustomer`,再用 `resumeGuestSession` 取得游客 `customerToken`。浏览器只接收这一运行的 `runToken`。独立 Paywall 使用系统维护的 Funnel。
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import {
|
|
58
|
+
createFunnelRunController,
|
|
59
|
+
showFunnelRun,
|
|
60
|
+
} from "@tinkerlandai/indiana-sdk";
|
|
61
|
+
const client = createFunnelRunController({
|
|
62
|
+
baseUrl,
|
|
63
|
+
scope: { projectId, environment: "sandbox" },
|
|
64
|
+
runToken,
|
|
65
|
+
});
|
|
66
|
+
const handle = await showFunnelRun(container, {
|
|
67
|
+
client,
|
|
68
|
+
onAuthenticate: async () => obtainCurrentUserCustomerToken(),
|
|
69
|
+
onCheckout: ({ url }) => window.location.assign(url),
|
|
70
|
+
onOpenUrl: ({ url }) => window.location.assign(url),
|
|
71
|
+
onError: reportError,
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
宿主实现登录与 Token 获取;取消认证返回 `null`。付款回跳后展示同一运行,服务端读取验证后的支付事实。点击、导航和宿主回调不能确认付款。
|
|
76
|
+
|
|
77
|
+
控制器提供 `get`、`submit`、`retry`、`resume`、`identify`、`link`、`subscribe` 和 `reset`。变更命令包含当前 `nodeId`、`revision` 与稳定 `requestId`。超时保留 `pendingCommand`,先 `retry` 原命令;服务端已经持有中断命令时使用其 `pendingRequest.requestId` 恢复。原命令回执不能倒退新的观测状态。
|
|
78
|
+
|
|
79
|
+
每个屏幕分别选择弹窗、面板、全屏或嵌入。屏幕按钮只产生出口,认证、领券、收费和会员变更由相应节点执行。账户、积分、会员和账单是屏幕中的客户组件;刷新、分页、过滤及预览只读。`destroy` 清理界面,`close` 才提交关闭命令。
|
|
80
|
+
|
|
81
|
+
## JSON 决策与 Feature(当前源码)
|
|
82
|
+
|
|
83
|
+
本版的通用 JSON 决策需要匹配的数据库迁移和 Workflow 部署。旧个人包 `@houzhen888/groove-grow@0.0.7` 使用发布时的旧契约;发布事实见 [发布总账](../../docs/sdk-releases.md)。
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { createFunnelDecisionClient } from "@tinkerlandai/indiana-sdk/server";
|
|
87
|
+
const decisions = createFunnelDecisionClient({
|
|
88
|
+
baseUrl,
|
|
89
|
+
scope,
|
|
90
|
+
apiKey,
|
|
91
|
+
funnelId,
|
|
92
|
+
});
|
|
93
|
+
const decision = await decisions.decide({
|
|
94
|
+
requestId: retainedRequestId,
|
|
95
|
+
customerId,
|
|
96
|
+
attributes: {
|
|
97
|
+
tags: ["ad"],
|
|
98
|
+
isNeverPaid: true,
|
|
99
|
+
country: "US",
|
|
100
|
+
channel: "ads",
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
可信后端提交完整 `attributes` JSON,业务可以自行提供付费历史、渠道等属性。结果的 `value` 是规则输出的任意 JSON;首次成功提交的结果(包括 null、false、0 或零优惠对象)固定在当前 allocationScopeId,后续属性变化不会重新计算。`state` 分别表示 active、expired 或 inactive;它随有效期和活动状态变化,value 保持不变。新的分配活动须显式更换 allocationScopeId。parameters、answers、results 是 Funnel 保留的上下文命名空间。
|
|
106
|
+
|
|
107
|
+
每个新 requestId 保存查询日志,重试保留原 ID。查询不发券、不执行支付。采用后可选调用 `reportUsed({ eventId, decisionId, occurredAt, reference })`;它只记录调用方报告。超时后的 `FunnelDecisionTimeoutError` 保留 decisionId,可通过 read 继续读取。
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const features = decisions.features({
|
|
111
|
+
attributes: { id: customerId, country: "US" },
|
|
112
|
+
});
|
|
113
|
+
await features.init(); // 下载当前 App/环境/Funnel 的配置
|
|
114
|
+
const value = features.getFeatureValue("discount", { starter: 0 }); // 同步、本地判断
|
|
115
|
+
features.setAttributes({ id: customerId, country: "JP" }); // 完整替换属性
|
|
116
|
+
await features.refresh(); // 显式获取新的资源配置
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`createFeatureClient` 在浏览器与服务端入口均可用,自行提供 load 可复用安全下发的配置。Secret Key 仅用于后端。init/refresh 后,每次 evalFeature/getFeatureValue 不联网;expiresAt 到达时间表边界后必须刷新。实验曝光和 Feature 使用由可选回调报告;持久化粘性分桶需提供对应存储回调和初始分配,后端 Funnel 使用原子数据库存储。
|
|
120
|
+
|
|
121
|
+
需要异步接收时调用 `submit(input)`,它返回已持久接受的当前状态,不等待求值或轮询。`submit`/`decide` 首次提交的响应丢失、网络错误或服务暂时不可用时,会抛出 `DECISION_ACCEPTANCE_UNKNOWN`,其 `requestId` 是原逻辑请求编号;沿用原编号与原输入重试。请求已接受后,HTTP 结果等待最多八秒,读取卡住或暂时失败仍返回原 requestId/decisionId 的 pending 状态,不取消后台执行。
|
|
122
|
+
|
|
123
|
+
交互运行的 `runExpiresAt` 和 `credentialExpiresAt` 分别表示业务期限和凭证期限。可信后端使用 `renewFunnelRun` 续签同一已识别客户的运行,业务期限不变。等待指令和客户端须使用同一当前协议。
|
|
124
|
+
|
|
125
|
+
## Funnel 服务端回执
|
|
126
|
+
|
|
127
|
+
`createFunnelOrchestrationClient` 来自 `/server` 入口,使用 App/环境的 Secret Key。`signal({ runId, nodeId, eventName, eventId, data })` 为已声明的自定义等待节点提交回执,重试沿用同一事件 ID 与数据;`readResults(runId)` 读取已完成任务和已消费回执的结果。浏览器不能发送这些回执,自定义事件不能代替验证付款事实。
|
|
128
|
+
|
|
129
|
+
回执可以早于等待节点抵达,但收到确认不代表已经消费。条件和后续任务以 `nodeId.field` 引用声明结果。仅查询优惠的调用方可以继续使用上面的查询接口,不需要增加回执等待。
|
|
130
|
+
|
|
131
|
+
本版包含 `createFunnelOrchestrationClient` 的 `signal` / `readResults` 接口。外部任务可复用已启用的 Sandbox 通知端点,提供签名请求、冻结输入和持久重试预算。接收器必须按 Action Receipt ID 去重;网络调用允许至少一次投递。配置、协议、数据库变更与验收边界见[编排说明](../../docs/funnel-orchestration-2026-10-10.md)。接口存在不代表宿主通知已经配置或验收。
|
|
132
|
+
|
|
133
|
+
## 当前持有与账本
|
|
134
|
+
|
|
135
|
+
### 原生订单恢复
|
|
136
|
+
|
|
137
|
+
Swift/Kotlin 宿主的 `recoverNativeOrders` 通过应用自有 HTTPS 后端提交当前客户会话及受保护的订单绑定。后端使用 `createNativeReceiptClient`,服务器密钥始终留在后端:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import { createNativeReceiptClient } from "@tinkerlandai/indiana-sdk/server";
|
|
141
|
+
const receipts = createNativeReceiptClient({
|
|
142
|
+
baseUrl: process.env.GROOVE_BASE_URL!,
|
|
143
|
+
apiKey: process.env.GROOVE_SECRET_KEY!,
|
|
144
|
+
scope: { projectId: process.env.GROOVE_PROJECT_ID!, environment: "sandbox" },
|
|
145
|
+
});
|
|
146
|
+
// 先校验应用自身登录会话及当前客户身份;重试保留原 requestId 和输入。
|
|
147
|
+
const result = await receipts.recover(retainedCurrentReceiptInput);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
该操作为 `/api/v1/funnels` 的 `native_receipt`,需要 `billing:write` 与 `funnels:run` 以及当前 `gg_ct_` 客户会话。它不依赖 run token,不复活或推进已结束、关闭或过期流程。Apple 只 finish 返回的 `nativeCompletion.transactionIds`;Google 的 acknowledge/consume 在服务端持久交付后完成。完整输入、宿主 relay、固定 SKU 席位及商店优惠边界见[当前原生接入](../../docs/native-commercial-current.md)。
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import { createGroove } from "@tinkerlandai/indiana-sdk";
|
|
154
|
+
const grow = createGroove({
|
|
155
|
+
baseUrl,
|
|
156
|
+
scope: { projectId, environment: "sandbox" },
|
|
157
|
+
});
|
|
158
|
+
grow.identify(customerToken);
|
|
159
|
+
const { holdings } = await grow.readHoldings();
|
|
160
|
+
const { ledger } = await grow.readLedger({
|
|
161
|
+
type: null,
|
|
162
|
+
from: null,
|
|
163
|
+
to: null,
|
|
164
|
+
after: null,
|
|
165
|
+
limit: 50,
|
|
166
|
+
});
|
|
167
|
+
const { quote } = await grow.quoteBalance({
|
|
168
|
+
operation: "quote",
|
|
169
|
+
entitlementId,
|
|
170
|
+
quantity: "1",
|
|
171
|
+
properties: {},
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
浏览器请求不传客户 ID。持有需要 `entitlements:read`,账本和报价需要 `credits:read`。账本过滤作用于全部历史,之后分页;下一页原样传回 `nextCursor`,保留微秒、记录 ID 与类型。数量、余额和计价保留十进制字符串;报价不扣款。
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
const { receipt } = await groove.mutateBalance({
|
|
179
|
+
customerId: verifiedCustomer.id,
|
|
180
|
+
command: {
|
|
181
|
+
operation: "consume",
|
|
182
|
+
entitlementId,
|
|
183
|
+
quantity: "1",
|
|
184
|
+
properties: {},
|
|
185
|
+
idempotencyKey: `job:${job.id}`,
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
后端使用 `consume`、`reserve`、`confirm`、`release`、`refund`。每次重试保留原输入和幂等键,确认、释放与退款引用原操作 ID。写入需要 `credits:consume`;退款另外需要 `credits:manage`。当前余额执行仅开放明确的 Sandbox 范围。
|
|
191
|
+
|
|
192
|
+
## 积分、用量与计费
|
|
193
|
+
|
|
194
|
+
以下接口需要与 SDK 协议匹配的 Grow 部署。SDK 包验证与发布不会自动升级既有宿主;数据库、Stripe、原生商店和部署各自的验收结果以对应回执为准。
|
|
195
|
+
|
|
196
|
+
### 周期积分、消费分析与批量用量
|
|
197
|
+
|
|
198
|
+
接口与验收边界见 [积分能力补齐](../../docs/credit-expansion.md)。在可信服务端使用:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
const { entity } = await grow.manageUsageEntity({
|
|
202
|
+
customerId,
|
|
203
|
+
command: { action: "upsert", externalId: "workspace-a", name: "Workspace A" },
|
|
204
|
+
});
|
|
205
|
+
const requestId = crypto.randomUUID(); // 保存这个逻辑批次的身份,重试时复用
|
|
206
|
+
const { batchId } = await grow.submitUsageBatch({
|
|
207
|
+
requestId,
|
|
208
|
+
items: [
|
|
209
|
+
{
|
|
210
|
+
customerId,
|
|
211
|
+
command: {
|
|
212
|
+
operation: "consume",
|
|
213
|
+
entitlementId,
|
|
214
|
+
entityId: entity.id,
|
|
215
|
+
quantity: "1",
|
|
216
|
+
properties: { model: "alpha" },
|
|
217
|
+
idempotencyKey: "generation-001",
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
});
|
|
222
|
+
const { batch } = await grow.readUsageBatch({ batchId, after: -1, limit: 100 });
|
|
223
|
+
// queued 只表示等待执行;逐条检查 accepted / rejected / failed 及 receipt。
|
|
224
|
+
// 后续页使用 batch.nextAfter;整个批次没有跨功能事务语义。
|
|
225
|
+
const { analytics } = await grow.readCreditAnalytics({
|
|
226
|
+
customerId,
|
|
227
|
+
query: {
|
|
228
|
+
range: {
|
|
229
|
+
kind: "absolute",
|
|
230
|
+
from: "2026-10-01T00:00:00Z",
|
|
231
|
+
to: "2026-10-08T00:00:00Z",
|
|
232
|
+
},
|
|
233
|
+
timezone: "UTC",
|
|
234
|
+
filterBy: [],
|
|
235
|
+
interval: "day",
|
|
236
|
+
groupBy: "properties.model",
|
|
237
|
+
entitlementId: null,
|
|
238
|
+
after: null,
|
|
239
|
+
limit: 100,
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
`manageUsageEntity` 需要 `credits:manage`;批量提交需要 `credits:consume`,读取和分析需要 `credits:read`。批次最多 1,000 条,入队后返回 HTTP 202,每条事件保留独立消费凭据。相同 `requestId` 的重试必须保留完整输入;修改内容会冲突。分析保留 `entitlementId` 与 `resourceId`,不同计量单位不能直接相加,`charged` 等字段是积分数量。
|
|
245
|
+
|
|
246
|
+
批量消费可指定 `command.occurredAt`;只允许当前 UTC 月内且不晚于服务器当前时间的事件。该时间用于事件归属,不回溯重算已结算账期或过去套餐的额度与费率。相对分析范围可用 `last_days` 或客户的 `billing_cycles`;读取后续页时,将 `range` 固定为返回的 `analytics.from` / `analytics.to`(`kind: "absolute"`),并传入 `analytics.nextCursor`,避免相对日期窗口在分页时移动。小时分桶使用 UTC,其余分桶使用查询的 IANA 时区。
|
|
247
|
+
|
|
248
|
+
服务端 AI SDK v7 的自动 Token 采集用 `createAiUsageMiddleware`,必须提供每次上游调用的持久幂等键与 usage/quote 存储回调。生成和流式最终用量均使用原报价一次扣减;失败恢复直接重放扣款,避免重跑模型。`readBillingInvoice`、`readCreditStatement` 提供分页逐项账单和精确来源/退款明细。接入代码、时间范围与当前边界见 [积分接入](../../docs/credits-integration.md)。后续协议扩展须单独发布并匹配服务端部署。
|
|
249
|
+
|
|
250
|
+
### 余额规则、状态同步与 AI 配置
|
|
251
|
+
|
|
252
|
+
Server SDK 的 `readBalanceSource` / `adjustBalance` 在读取精确可用余额后以 `expectedAvailable` 增减或 `set_remaining`;保留预扣、已消费及付款依据。独立 `manageBalanceGrant` 的创建/更新使用当前 `rollover`、`nextResetAt` 字段,`amount: null` 表示无限额度,不能与结转组合。没有旧输入兼容。
|
|
253
|
+
|
|
254
|
+
非消耗型的实际状态也通过当前客户的 `readHoldings()` 返回(`entitlements:read`):每行必含可空 `usage`;非消耗型的 `usage` 含实际占用、总容量、带符号剩余量及修订号,其他类型为 null。无限容量的 capacity / remaining 为 null,超容量的 remaining 为负;available 始终非负。指定 Entity 只计算其独立非消耗型容量。
|
|
255
|
+
|
|
256
|
+
非消耗型 metered 权益使用绝对状态同步,读取需要 `credits:read`,设置需要 `credits:manage`:
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
const { usage } = await grow.readUsage({
|
|
260
|
+
customerId,
|
|
261
|
+
query: { entitlementId, entityId: null },
|
|
262
|
+
});
|
|
263
|
+
await grow.setUsage({
|
|
264
|
+
customerId,
|
|
265
|
+
command: {
|
|
266
|
+
entitlementId,
|
|
267
|
+
entityId: null,
|
|
268
|
+
quantity: "12",
|
|
269
|
+
expectedRevision: usage.revision,
|
|
270
|
+
idempotencyKey: durableSyncId,
|
|
271
|
+
properties: {},
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
`createConfiguredAiPricing(grow, { entitlementId })` 从已发布权益读取控制台 AI 模型/加价配置,入口为 `@tinkerlandai/indiana-sdk/server`。支持负加价和显式免费价格,免费调用保留真实 Token 的零金额事件。`createOpenRouterUsageClient` 从独立入口 `@tinkerlandai/indiana-sdk/openrouter` 导入,包装当前 `@openrouter/sdk` 的 `chat.send`,自动捕获生成/流式 Token,先持久保存 usage/原报价,再原子扣款;需要宿主提供 `context`、`persist` 和明确的推理计价方式。只有使用该入口的应用需要安装可选 peer dependency `@openrouter/sdk`;普通服务端入口的类型不引用它。示例及缺失价格、取消流、并发冲突和恢复语义见 [当前接入](../../docs/credits-integration.md)。
|
|
277
|
+
|
|
278
|
+
### 新购订阅选择优惠券
|
|
279
|
+
|
|
280
|
+
先通过 `redeemCoupon` 或发放获得持券,再用 `listOffers` 查询;将所选持券的 `id` 传入 `previewAttach.offerId`,不是 Coupon 定义 ID。仅新购 Stripe Checkout 支持此输入,首次结账最多一张券;已有订阅使用现有订阅优惠操作。
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
const { quote } = await groove.previewAttach({
|
|
284
|
+
requestId: crypto.randomUUID(),
|
|
285
|
+
plans: [{ productVersionId }],
|
|
286
|
+
provider: "stripe",
|
|
287
|
+
currency: "USD",
|
|
288
|
+
offerId: selectedOffer.id,
|
|
289
|
+
});
|
|
290
|
+
// 向客户展示 quote.discount 的条款、amountMinor 和逐套餐 allocations。
|
|
291
|
+
// quote.catalogAmountMinor 是原价,自动税额在原生 Checkout 中确认。
|
|
292
|
+
const { operation } = await groove.attach({ operationId: quote.operationId });
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
报价固定客户、环境、商品版本、币种和持券;修改选择必须使用新的 requestId 重新报价。接受时重新核对资格及持券占用,重复确认恢复原订单。只有原生支付验证才能产生核销和付费权益。
|
|
296
|
+
|
|
297
|
+
## 0.0.1 范围与验收边界
|
|
298
|
+
|
|
299
|
+
本版包含下列 Web/TypeScript 接口及行为。原生配套包、数据库与服务部署按各自回执判断:
|
|
300
|
+
|
|
301
|
+
- 宿主句柄也支持 `handle.resume(originalRequestId)`,可在原命令已经完成后回读同一回执;无参数时恢复当前服务端待处理请求。`close()` 等待正在提交的命令完成,再持久化关闭,提交失败会继续向宿主抛出错误。 旧个人包 `0.0.7` 不代表具备相同语义。
|
|
302
|
+
- 本版包含独立商店路径的 Web 契约;Swift/Kotlin 配套包另行发布:`previewAttach`/`previewUpdate` 固定订阅操作后,Swift/Kotlin 的 `purchaseSubscription`/`switchSubscription` 获取实际商店报价并购买;`restorePurchases` 扫描当前购买,通过 `POST /api/v1/subscriptions/native/restore` 恢复,不要求本地保留原订单绑定。后端仍须校验当前客户会话;商店购买链与账户关联决定归属,客户端的意图或客户 ID 不能转移购买。独立恢复要求 `billing:write`,已知 Funnel 订单的回执恢复仍使用上面的协议。候选迁移、本地替身验证与真实验收边界见[第八批记录](../../docs/subscription-core-phase8.md)。
|
|
303
|
+
- 本版的预付 Feature 数量接口:`previewAttach` 的每个 plan 可传 `featureQuantities: { [entitlementVersionId]: quantity }`;`previewUpdate` 使用 `change: { type: "quantity", entitlementVersionId, quantity }`,再通过同一 `update` 确认。Swift/Kotlin 对应 `previewQuantityUpdate`。增加数量可立即付款生效,减少数量使用 `renewal`;重试保留原 requestId。服务端根据不可变套餐条款和实际计费项交付额度。此项支持 Stripe,Apple/Google 仍使用固定已发布 SKU,不能传可变数量替换商店价格。详见[第九批记录](../../docs/subscription-core-phase9.md)。
|
|
304
|
+
- 本版客户资料使用 `attributes`,属性删除使用 `removeAttributes`,标签增减使用 `tagChanges`;旧个人包 `0.0.7` 保留原 `metadata` / `tags` 契约,不能向旧包传入当前新字段。
|
|
305
|
+
- 本版浏览器和服务端提供 `createFeatureClient` 与 Feature 评估契约,见[实施记录](../../docs/experiments-growthbook-port-plan-2026-10-11.md)。该导出不在旧个人包 `0.0.7` 的类型入口中。
|
|
306
|
+
|
|
307
|
+
发布产物和检查结果见 [发布总账](../../docs/sdk-releases.md)。本次按用户要求跳过浏览器验证;真实 Auth/RLS、Stripe、原生商店与部署验收未执行,npm 发布不代表这些验收已通过。
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# GrowthBook core
|
|
2
|
+
|
|
3
|
+
MIT core port from growthbook/growthbook at ed29827a453ae6b9eba181bdf4e9280d4b670817. No enterprise-licensed code is included.
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2025 GrowthBook, Inc.
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { LanguageModelV4Usage, LanguageModelV4Middleware } from '@ai-sdk/provider';
|
|
2
|
+
import { F as FoundationApi, A as ApiInput } from './api-DDlcUvTv.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
declare const tokenCounts: z.ZodObject<{
|
|
6
|
+
input: z.ZodNumber;
|
|
7
|
+
output: z.ZodNumber;
|
|
8
|
+
cache_read: z.ZodNumber;
|
|
9
|
+
cache_write: z.ZodNumber;
|
|
10
|
+
reasoning: z.ZodNumber;
|
|
11
|
+
input_audio: z.ZodNumber;
|
|
12
|
+
output_audio: z.ZodNumber;
|
|
13
|
+
}, z.core.$strict>;
|
|
14
|
+
declare const aiPriceSnapshotSchema: z.ZodObject<{
|
|
15
|
+
modelId: z.ZodString;
|
|
16
|
+
source: z.ZodEnum<{
|
|
17
|
+
"models.dev": "models.dev";
|
|
18
|
+
custom: "custom";
|
|
19
|
+
}>;
|
|
20
|
+
fetchedAt: z.ZodISODateTime;
|
|
21
|
+
rates: z.ZodObject<{
|
|
22
|
+
input: z.ZodString;
|
|
23
|
+
output: z.ZodString;
|
|
24
|
+
cache_read: z.ZodOptional<z.ZodString>;
|
|
25
|
+
cache_write: z.ZodOptional<z.ZodString>;
|
|
26
|
+
reasoning: z.ZodOptional<z.ZodString>;
|
|
27
|
+
input_audio: z.ZodOptional<z.ZodString>;
|
|
28
|
+
output_audio: z.ZodOptional<z.ZodString>;
|
|
29
|
+
}, z.core.$strict>;
|
|
30
|
+
contextTier: z.ZodNumber;
|
|
31
|
+
markupPercent: z.ZodNumber;
|
|
32
|
+
}, z.core.$strict>;
|
|
33
|
+
declare const aiTokenQuoteSchema: z.ZodObject<{
|
|
34
|
+
pricing: z.ZodObject<{
|
|
35
|
+
modelId: z.ZodString;
|
|
36
|
+
source: z.ZodEnum<{
|
|
37
|
+
"models.dev": "models.dev";
|
|
38
|
+
custom: "custom";
|
|
39
|
+
}>;
|
|
40
|
+
fetchedAt: z.ZodISODateTime;
|
|
41
|
+
rates: z.ZodObject<{
|
|
42
|
+
input: z.ZodString;
|
|
43
|
+
output: z.ZodString;
|
|
44
|
+
cache_read: z.ZodOptional<z.ZodString>;
|
|
45
|
+
cache_write: z.ZodOptional<z.ZodString>;
|
|
46
|
+
reasoning: z.ZodOptional<z.ZodString>;
|
|
47
|
+
input_audio: z.ZodOptional<z.ZodString>;
|
|
48
|
+
output_audio: z.ZodOptional<z.ZodString>;
|
|
49
|
+
}, z.core.$strict>;
|
|
50
|
+
contextTier: z.ZodNumber;
|
|
51
|
+
markupPercent: z.ZodNumber;
|
|
52
|
+
}, z.core.$strict>;
|
|
53
|
+
tokens: z.ZodObject<{
|
|
54
|
+
input: z.ZodNumber;
|
|
55
|
+
output: z.ZodNumber;
|
|
56
|
+
cache_read: z.ZodNumber;
|
|
57
|
+
cache_write: z.ZodNumber;
|
|
58
|
+
reasoning: z.ZodNumber;
|
|
59
|
+
input_audio: z.ZodNumber;
|
|
60
|
+
output_audio: z.ZodNumber;
|
|
61
|
+
}, z.core.$strict>;
|
|
62
|
+
quantity: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
63
|
+
digest: z.ZodString;
|
|
64
|
+
}, z.core.$strict>;
|
|
65
|
+
type AiTokenQuote = z.infer<typeof aiTokenQuoteSchema>;
|
|
66
|
+
type AiTokenUsage = z.infer<typeof tokenCounts>;
|
|
67
|
+
type AiPricingOptions = {
|
|
68
|
+
defaultMarkup?: number;
|
|
69
|
+
providerMarkups?: Record<string, number>;
|
|
70
|
+
modelMarkups?: Record<string, number>;
|
|
71
|
+
customModels?: Record<string, z.infer<typeof aiPriceSnapshotSchema>["rates"]>;
|
|
72
|
+
fetch?: typeof fetch;
|
|
73
|
+
};
|
|
74
|
+
/** Fetch automatic model prices on a trusted backend. Quotes can be saved with a job and reused verbatim on retries. */
|
|
75
|
+
declare function createAiPricing(options?: AiPricingOptions): {
|
|
76
|
+
/** Explicit refresh affects future quotes only, including their immutable timestamps and digest. */
|
|
77
|
+
refreshPrices(): void;
|
|
78
|
+
quoteTokens(input: {
|
|
79
|
+
modelId: string;
|
|
80
|
+
tokens: AiTokenUsage;
|
|
81
|
+
}): Promise<AiTokenQuote>;
|
|
82
|
+
/** All pools become one atomic balance operation. The metered entitlement unit must be one micro USD. */
|
|
83
|
+
trackTokens(client: Pick<FoundationApi, "mutateBalance">, input: {
|
|
84
|
+
customerId: string;
|
|
85
|
+
entitlementId: string;
|
|
86
|
+
entityId?: string;
|
|
87
|
+
idempotencyKey: string;
|
|
88
|
+
quote: AiTokenQuote;
|
|
89
|
+
properties?: Record<string, string>;
|
|
90
|
+
providerCostUsd?: string;
|
|
91
|
+
generationId?: string;
|
|
92
|
+
}): Promise<{
|
|
93
|
+
requestId: string;
|
|
94
|
+
scope: {
|
|
95
|
+
projectId: string;
|
|
96
|
+
environment: "sandbox" | "live";
|
|
97
|
+
};
|
|
98
|
+
receipt: {
|
|
99
|
+
id: string;
|
|
100
|
+
entitlementId: string;
|
|
101
|
+
kind: "confirm" | "consume" | "reserve" | "refund" | "release" | "expire";
|
|
102
|
+
originalId: string | null;
|
|
103
|
+
quantity: string;
|
|
104
|
+
accepted: boolean;
|
|
105
|
+
reason: "expired" | "unavailable" | "insufficient_balance" | "limit_exceeded" | "awaiting_topup" | "authorization_required" | "payment_blocked" | "cash_budget_exceeded" | null;
|
|
106
|
+
createdAt: string;
|
|
107
|
+
reservationStatus: "pending" | "expired" | "confirmed" | "released" | null;
|
|
108
|
+
expiresAt: string | null;
|
|
109
|
+
allocations: ({
|
|
110
|
+
funding: "prepaid";
|
|
111
|
+
sourceId: string;
|
|
112
|
+
units: string;
|
|
113
|
+
amount: string;
|
|
114
|
+
pricing: {
|
|
115
|
+
creditVersionId: string | null;
|
|
116
|
+
meteredVersionId: string;
|
|
117
|
+
dimension: string;
|
|
118
|
+
counterKey: string;
|
|
119
|
+
cycleStart: string;
|
|
120
|
+
unitCost: string;
|
|
121
|
+
units: string;
|
|
122
|
+
amount: string;
|
|
123
|
+
tier: number | null;
|
|
124
|
+
}[];
|
|
125
|
+
} | {
|
|
126
|
+
funding: "unbilled";
|
|
127
|
+
units: string;
|
|
128
|
+
} | {
|
|
129
|
+
funding: "postpaid";
|
|
130
|
+
invoiceLineId: string | null;
|
|
131
|
+
units: string;
|
|
132
|
+
authorizationId: string;
|
|
133
|
+
})[];
|
|
134
|
+
requestedQuantity?: string | undefined;
|
|
135
|
+
overageBehavior?: "cap" | "overflow" | undefined;
|
|
136
|
+
};
|
|
137
|
+
}>;
|
|
138
|
+
};
|
|
139
|
+
/** Read one published configuration for new provider calls. Saved quotes keep their original rules. */
|
|
140
|
+
declare function createConfiguredAiPricing(client: Pick<FoundationApi, "readAiPricing">, input: ApiInput<"readAiPricing">, options?: Pick<AiPricingOptions, "fetch">): Promise<{
|
|
141
|
+
/** Explicit refresh affects future quotes only, including their immutable timestamps and digest. */
|
|
142
|
+
refreshPrices(): void;
|
|
143
|
+
quoteTokens(input: {
|
|
144
|
+
modelId: string;
|
|
145
|
+
tokens: AiTokenUsage;
|
|
146
|
+
}): Promise<AiTokenQuote>;
|
|
147
|
+
/** All pools become one atomic balance operation. The metered entitlement unit must be one micro USD. */
|
|
148
|
+
trackTokens(client: Pick<FoundationApi, "mutateBalance">, input: {
|
|
149
|
+
customerId: string;
|
|
150
|
+
entitlementId: string;
|
|
151
|
+
entityId?: string;
|
|
152
|
+
idempotencyKey: string;
|
|
153
|
+
quote: AiTokenQuote;
|
|
154
|
+
properties?: Record<string, string>;
|
|
155
|
+
providerCostUsd?: string;
|
|
156
|
+
generationId?: string;
|
|
157
|
+
}): Promise<{
|
|
158
|
+
requestId: string;
|
|
159
|
+
scope: {
|
|
160
|
+
projectId: string;
|
|
161
|
+
environment: "sandbox" | "live";
|
|
162
|
+
};
|
|
163
|
+
receipt: {
|
|
164
|
+
id: string;
|
|
165
|
+
entitlementId: string;
|
|
166
|
+
kind: "confirm" | "consume" | "reserve" | "refund" | "release" | "expire";
|
|
167
|
+
originalId: string | null;
|
|
168
|
+
quantity: string;
|
|
169
|
+
accepted: boolean;
|
|
170
|
+
reason: "expired" | "unavailable" | "insufficient_balance" | "limit_exceeded" | "awaiting_topup" | "authorization_required" | "payment_blocked" | "cash_budget_exceeded" | null;
|
|
171
|
+
createdAt: string;
|
|
172
|
+
reservationStatus: "pending" | "expired" | "confirmed" | "released" | null;
|
|
173
|
+
expiresAt: string | null;
|
|
174
|
+
allocations: ({
|
|
175
|
+
funding: "prepaid";
|
|
176
|
+
sourceId: string;
|
|
177
|
+
units: string;
|
|
178
|
+
amount: string;
|
|
179
|
+
pricing: {
|
|
180
|
+
creditVersionId: string | null;
|
|
181
|
+
meteredVersionId: string;
|
|
182
|
+
dimension: string;
|
|
183
|
+
counterKey: string;
|
|
184
|
+
cycleStart: string;
|
|
185
|
+
unitCost: string;
|
|
186
|
+
units: string;
|
|
187
|
+
amount: string;
|
|
188
|
+
tier: number | null;
|
|
189
|
+
}[];
|
|
190
|
+
} | {
|
|
191
|
+
funding: "unbilled";
|
|
192
|
+
units: string;
|
|
193
|
+
} | {
|
|
194
|
+
funding: "postpaid";
|
|
195
|
+
invoiceLineId: string | null;
|
|
196
|
+
units: string;
|
|
197
|
+
authorizationId: string;
|
|
198
|
+
})[];
|
|
199
|
+
requestedQuantity?: string | undefined;
|
|
200
|
+
overageBehavior?: "cap" | "overflow" | undefined;
|
|
201
|
+
};
|
|
202
|
+
}>;
|
|
203
|
+
}>;
|
|
204
|
+
|
|
205
|
+
type AiUsageContext = {
|
|
206
|
+
customerId: string;
|
|
207
|
+
entitlementId: string;
|
|
208
|
+
entityId?: string;
|
|
209
|
+
idempotencyKey: string;
|
|
210
|
+
properties?: Record<string, string>;
|
|
211
|
+
};
|
|
212
|
+
type AiUsageRecord = {
|
|
213
|
+
context: AiUsageContext;
|
|
214
|
+
modelId: string;
|
|
215
|
+
usage: LanguageModelV4Usage | null;
|
|
216
|
+
tokens: AiTokenUsage | null;
|
|
217
|
+
quote: AiTokenQuote | null;
|
|
218
|
+
receipt: Awaited<ReturnType<FoundationApi["mutateBalance"]>> | null;
|
|
219
|
+
phase: "captured" | "quoted" | "settled" | "incomplete";
|
|
220
|
+
};
|
|
221
|
+
type AiUsageMiddlewareOptions = {
|
|
222
|
+
pricing: ReturnType<typeof createAiPricing>;
|
|
223
|
+
client: Pick<FoundationApi, "mutateBalance">;
|
|
224
|
+
modelId: string;
|
|
225
|
+
/** Allocate a durable, unique ID per provider call, including each tool step. */
|
|
226
|
+
context: () => AiUsageContext | Promise<AiUsageContext>;
|
|
227
|
+
/** Save raw usage before pricing, and the frozen quote before a balance request. */
|
|
228
|
+
persist: (record: AiUsageRecord) => Promise<void>;
|
|
229
|
+
reasoning: "output" | "separate";
|
|
230
|
+
mapTokens?: (usage: LanguageModelV4Usage) => AiTokenUsage;
|
|
231
|
+
};
|
|
232
|
+
/** Current AI SDK v7 / LanguageModelV4 only. Never suppress a failed financial settlement. */
|
|
233
|
+
declare function createAiUsageMiddleware(options: AiUsageMiddlewareOptions): LanguageModelV4Middleware;
|
|
234
|
+
|
|
235
|
+
export { type AiUsageContext as A, type AiTokenUsage as a, type AiTokenQuote as b, createAiPricing as c, type AiPricingOptions as d, type AiUsageMiddlewareOptions as e, type AiUsageRecord as f, aiPriceSnapshotSchema as g, aiTokenQuoteSchema as h, createAiUsageMiddleware as i, createConfiguredAiPricing as j };
|